Simpletest Coverage - modules/taxonomy/taxonomy.module

1 <?php
2 // $Id: taxonomy.module,v 1.495 2009/08/11 15:50:56 webchick 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 return array(
14 'administer taxonomy' => array(
15 'title' => t('Administer taxonomy'),
16 'description' => t('Manage taxonomy vocabularies and terms.'),
17 ),
18 );
19 }
20
21 /**
22 * Implement hook_fieldable_info().
23 */
24 function taxonomy_fieldable_info() {
25 $return = array(
26 'taxonomy_term' => array(
27 'label' => t('Taxonomy term'),
28 'object keys' => array(
29 'id' => 'tid',
30 'bundle' => 'vocabulary_machine_name',
31 ),
32 'bundle keys' => array(
33 'bundle' => 'machine_name',
34 ),
35 'bundles' => array(),
36 ),
37 );
38 foreach (taxonomy_get_vocabularies() as $vocabulary) {
39 $return['taxonomy_term']['bundles'][$vocabulary->machine_name] = array(
40 'label' => $vocabulary->name,
41 'admin' => array(
42 'path' => 'admin/structure/taxonomy/%taxonomy_vocabulary',
43 'real path' => 'admin/structure/taxonomy/' . $vocabulary->vid,
44 'bundle argument' => 3,
45 'access arguments' => array('administer taxonomy'),
46 ),
47 );
48 }
49 return $return;
50 }
51
52 /**
53 * Implement hook_field_build_modes();
54 *
55 * @TODO: build mode for display as a field (when attached to nodes etc.).
56 */
57 function taxonomy_field_build_modes($obj_type) {
58 $modes = array();
59 if ($obj_type == 'term') {
60 $modes = array(
61 'full' => t('Taxonomy term page'),
62 );
63 }
64 return $modes;
65 }
66
67 /**
68 * Implement hook_theme().
69 */
70 function taxonomy_theme() {
71 return array(
72 'taxonomy_term_select' => array(
73 'arguments' => array('element' => NULL),
74 ),
75 'taxonomy_overview_vocabularies' => array(
76 'arguments' => array('form' => array()),
77 ),
78 'taxonomy_overview_terms' => array(
79 'arguments' => array('form' => array()),
80 ),
81 'field_formatter_taxonomy_term_link' => array(
82 'arguments' => array('element' => NULL),
83 ),
84 'field_formatter_taxonomy_term_plain' => array(
85 'arguments' => array('element' => NULL),
86 ),
87 );
88 }
89
90 /**
91 * Implement hook_node_view().
92 */
93 function taxonomy_node_view($node, $build_mode) {
94 if (empty($node->taxonomy)) {
95 return;
96 }
97
98 if ($build_mode == 'rss') {
99 // Provide category information for RSS feeds.
100 foreach ($node->taxonomy as $term) {
101 $node->rss_elements[] = array(
102 'key' => 'category',
103 'value' => $term->name,
104 'attributes' => array('domain' => url(taxonomy_term_path($term), array('absolute' => TRUE))),
105 );
106 }
107 }
108 else {
109 $links = array();
110
111 // If previewing, the terms must be converted to objects first.
112 if (!empty($node->in_preview)) {
113 $node->taxonomy = taxonomy_preview_terms($node);
114 }
115
116 foreach ($node->taxonomy as $term) {
117 // During preview the free tagging terms are in an array unlike the
118 // other terms which are objects. So we have to check if a $term
119 // is an object or not.
120 if (is_object($term)) {
121 $links['taxonomy_term_' . $term->tid] = array(
122 'title' => $term->name,
123 'href' => taxonomy_term_path($term),
124 'attributes' => array('rel' => 'tag', 'title' => strip_tags($term->description))
125 );
126 }
127 // Previewing free tagging terms; we don't link them because the
128 // term-page might not exist yet.
129 else {
130 foreach ($term as $free_typed) {
131 $typed_terms = drupal_explode_tags($free_typed);
132 foreach ($typed_terms as $typed_term) {
133 $links['taxonomy_preview_term_' . $typed_term] = array(
134 'title' => $typed_term,
135 );
136 }
137 }
138 }
139 }
140
141 $node->content['links']['terms'] = array(
142 '#theme' => 'links',
143 '#links' => $links,
144 '#attributes' => array('class' => 'links inline'),
145 '#sorted' => TRUE,
146 );
147 }
148 }
149
150 /**
151 * For vocabularies not maintained by taxonomy.module, give the maintaining
152 * module a chance to provide a path for terms in that vocabulary.
153 *
154 * @param $term
155 * A term object.
156 * @return
157 * An internal Drupal path.
158 */
159 function taxonomy_term_path($term) {
160 $vocabulary = taxonomy_vocabulary_load($term->vid);
161 if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
162 return $path;
163 }
164 return 'taxonomy/term/' . $term->tid;
165 }
166
167 /**
168 * Implement hook_menu().
169 */
170 function taxonomy_menu() {
171 $items['admin/structure/taxonomy'] = array(
172 'title' => 'Taxonomy',
173 'description' => 'Manage tagging, categorization, and classification of your content.',
174 'page callback' => 'drupal_get_form',
175 'page arguments' => array('taxonomy_overview_vocabularies'),
176 'access arguments' => array('administer taxonomy'),
177 );
178
179 $items['admin/structure/taxonomy/list'] = array(
180 'title' => 'List',
181 'type' => MENU_DEFAULT_LOCAL_TASK,
182 'weight' => -10,
183 );
184
185 $items['admin/structure/taxonomy/add'] = array(
186 'title' => 'Add vocabulary',
187 'page callback' => 'drupal_get_form',
188 'page arguments' => array('taxonomy_form_vocabulary'),
189 'access arguments' => array('administer taxonomy'),
190 'type' => MENU_LOCAL_TASK,
191 );
192
193 $items['taxonomy/term/%taxonomy_term'] = array(
194 'title' => 'Taxonomy term',
195 'title callback' => 'taxonomy_term_title',
196 'title arguments' => array(2),
197 'page callback' => 'taxonomy_term_page',
198 'page arguments' => array(2),
199 'access arguments' => array('access content'),
200 'type' => MENU_CALLBACK,
201 );
202
203 $items['taxonomy/term/%taxonomy_term/view'] = array(
204 'title' => 'View',
205 'type' => MENU_DEFAULT_LOCAL_TASK,
206 );
207
208 $items['taxonomy/term/%taxonomy_term/edit'] = array(
209 'title' => 'Edit term',
210 'page callback' => 'taxonomy_term_edit',
211 'page arguments' => array(2),
212 'access arguments' => array('administer taxonomy'),
213 'type' => MENU_LOCAL_TASK,
214 'weight' => 10,
215 );
216 $items['taxonomy/term/%taxonomy_term/feed'] = array(
217 'title' => 'Taxonomy term',
218 'title callback' => 'taxonomy_term_title',
219 'title arguments' => array(2),
220 'page callback' => 'taxonomy_term_feed',
221 'page arguments' => array(2),
222 'access arguments' => array('access content'),
223 'type' => MENU_CALLBACK,
224 );
225 $items['taxonomy/autocomplete'] = array(
226 'title' => 'Autocomplete taxonomy',
227 'page callback' => 'taxonomy_autocomplete',
228 'access arguments' => array('access content'),
229 'type' => MENU_CALLBACK,
230 );
231
232 $items['admin/structure/taxonomy/%taxonomy_vocabulary'] = array(
233 'title' => 'Vocabulary', // this is replaced by callback
234 'page callback' => 'drupal_get_form',
235 'page arguments' => array('taxonomy_form_vocabulary', 3),
236 'title callback' => 'taxonomy_admin_vocabulary_title_callback',
237 'title arguments' => array(3),
238 'access arguments' => array('administer taxonomy'),
239 'type' => MENU_CALLBACK,
240 );
241
242 $items['admin/structure/taxonomy/%taxonomy_vocabulary/edit'] = array(
243 'title' => 'Edit vocabulary',
244 'type' => MENU_DEFAULT_LOCAL_TASK,
245 'weight' => -20,
246 );
247
248 $items['admin/structure/taxonomy/%taxonomy_vocabulary/list'] = array(
249 'title' => 'List terms',
250 'page callback' => 'drupal_get_form',
251 'page arguments' => array('taxonomy_overview_terms', 3),
252 'access arguments' => array('administer taxonomy'),
253 'type' => MENU_LOCAL_TASK,
254 'weight' => -10,
255 );
256
257 $items['admin/structure/taxonomy/%taxonomy_vocabulary/add'] = array(
258 'title' => 'Add term',
259 'page callback' => 'drupal_get_form',
260 'page arguments' => array('taxonomy_form_term', 3),
261 'access arguments' => array('administer taxonomy'),
262 'type' => MENU_LOCAL_TASK,
263 );
264
265 return $items;
266 }
267
268 /**
269 * Return the vocabulary name given the vocabulary object.
270 */
271 function taxonomy_admin_vocabulary_title_callback($vocabulary) {
272 return check_plain($vocabulary->name);
273 }
274
275 /**
276 * Save a vocabulary given a vocabulary object.
277 */
278 function taxonomy_vocabulary_save($vocabulary) {
279 if (empty($vocabulary->nodes)) {
280 $vocabulary->nodes = array();
281 }
282
283 if (!empty($vocabulary->name)) {
284 // Prevent leading and trailing spaces in vocabulary names.
285 $vocabulary->name = trim($vocabulary->name);
286 }
287
288 if (!isset($vocabulary->module)) {
289 $vocabulary->module = 'taxonomy';
290 }
291
292 if (!empty($vocabulary->vid) && !empty($vocabulary->name)) {
293 $status = drupal_write_record('taxonomy_vocabulary', $vocabulary, 'vid');
294 db_delete('taxonomy_vocabulary_node_type')
295 ->condition('vid', $vocabulary->vid)
296 ->execute();
297
298 if (!empty($vocabulary->nodes)) {
299 $query = db_insert('taxonomy_vocabulary_node_type')
300 ->fields(array('vid', 'type'));
301 foreach ($vocabulary->nodes as $type => $selected) {
302 $query->values(array(
303 'vid' => $vocabulary->vid,
304 'type' => $type,
305 ));
306 }
307 $query->execute();
308 }
309 module_invoke_all('taxonomy_vocabulary_update', $vocabulary);
310 }
311 elseif (empty($vocabulary->vid)) {
312 $status = drupal_write_record('taxonomy_vocabulary', $vocabulary);
313
314 if (!empty($vocabulary->nodes)) {
315 $query = db_insert('taxonomy_vocabulary_node_type')
316 ->fields(array('vid', 'type'));
317 foreach ($vocabulary->nodes as $type => $selected) {
318 $query->values(array(
319 'vid' => $vocabulary->vid,
320 'type' => $type,
321 ));
322 }
323 $query->execute();
324 }
325 field_attach_create_bundle($vocabulary->machine_name);
326 module_invoke_all('taxonomy_vocabulary_insert', $vocabulary);
327 }
328
329 cache_clear_all();
330 drupal_static_reset('taxonomy_vocabulary_load_multiple');
331
332 return $status;
333 }
334
335 /**
336 * Delete a vocabulary.
337 *
338 * @param $vid
339 * A vocabulary ID.
340 * @return
341 * Constant indicating items were deleted.
342 */
343 function taxonomy_vocabulary_delete($vid) {
344 $vocabulary = (array) taxonomy_vocabulary_load($vid);
345
346 db_delete('taxonomy_vocabulary')
347 ->condition('vid', $vid)
348 ->execute();
349 db_delete('taxonomy_vocabulary_node_type')
350 ->condition('vid', $vid)
351 ->execute();
352 $result = db_query('SELECT tid FROM {taxonomy_term_data} WHERE vid = :vid', array(':vid' => $vid))->fetchCol();
353 foreach ($result as $tid) {
354 taxonomy_term_delete($tid);
355 }
356
357 field_attach_delete_bundle($vocabulary['machine_name']);
358 module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
359
360 cache_clear_all();
361 drupal_static_reset('taxonomy_vocabulary_load_multiple');
362
363 return SAVED_DELETED;
364 }
365
366 /**
367 * Dynamically check and update the hierarchy flag of a vocabulary.
368 *
369 * Checks the current parents of all terms in a vocabulary and updates the
370 * vocabularies hierarchy setting to the lowest possible level. A hierarchy with
371 * no parents in any of its terms will be given a hierarchy of 0. If terms
372 * contain at most a single parent, the vocabulary will be given a hierarchy of
373 * 1. If any term contain multiple parents, the vocabulary will be given a
374 * hierarchy of 2.
375 *
376 * @param $vocabulary
377 * A vocabulary object.
378 * @param $changed_term
379 * An array of the term structure that was updated.
380 */
381 function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
382 $tree = taxonomy_get_tree($vocabulary->vid);
383 $hierarchy = 0;
384 foreach ($tree as $term) {
385 // Update the changed term with the new parent value before comparison.
386 if ($term->tid == $changed_term['tid']) {
387 $term = (object)$changed_term;
388 $term->parents = $term->parent;
389 }
390 // Check this term's parent count.
391 if (count($term->parents) > 1) {
392 $hierarchy = 2;
393 break;
394 }
395 elseif (count($term->parents) == 1 && 0 !== array_shift($term->parents)) {
396 $hierarchy = 1;
397 }
398 }
399 if ($hierarchy != $vocabulary->hierarchy) {
400 $vocabulary->hierarchy = $hierarchy;
401 taxonomy_vocabulary_save($vocabulary);
402 }
403
404 return $hierarchy;
405 }
406
407 /**
408 * Save a term object to the database.
409 *
410 * @param $term
411 * A term object.
412 * @return
413 * Status constant indicating if term was inserted or updated.
414 */
415 function taxonomy_term_save($term) {
416 if ($term->name) {
417 // Prevent leading and trailing spaces in term names.
418 $term->name = trim($term->name);
419 }
420 if (!isset($term->vocabulary_machine_name)) {
421 $vocabulary = taxonomy_vocabulary_load($term->vid);
422 $term->vocabulary_machine_name = $vocabulary->machine_name;
423 }
424
425 field_attach_presave('taxonomy_term', $term);
426
427 if (!empty($term->tid) && $term->name) {
428 $status = drupal_write_record('taxonomy_term_data', $term, 'tid');
429 field_attach_update('taxonomy_term', $term);
430 module_invoke_all('taxonomy_term_update', $term);
431 }
432 else {
433 $status = drupal_write_record('taxonomy_term_data', $term);
434 _taxonomy_clean_field_cache($term);
435 field_attach_insert('taxonomy_term', $term);
436 module_invoke_all('taxonomy_term_insert', $term);
437 }
438
439 db_delete('taxonomy_term_hierarchy')
440 ->condition('tid', $term->tid)
441 ->execute();
442
443 if (!isset($term->parent) || empty($term->parent)) {
444 $term->parent = array(0);
445 }
446 $query = db_insert('taxonomy_term_hierarchy')
447 ->fields(array('tid', 'parent'));
448 if (is_array($term->parent)) {
449 foreach ($term->parent as $parent) {
450 if (is_array($parent)) {
451 foreach ($parent as $tid) {
452 $query->values(array(
453 'tid' => $term->tid,
454 'parent' => $tid
455 ));
456 }
457 }
458 else {
459 $query->values(array(
460 'tid' => $term->tid,
461 'parent' => $parent
462 ));
463 }
464 }
465 }
466 else {
467 $query->values(array(
468 'tid' => $term->tid,
469 'parent' => $parent
470 ));
471 }
472 $query->execute();
473
474 db_delete('taxonomy_term_synonym')
475 ->condition('tid', $term->tid)
476 ->execute();
477 if (!empty($term->synonyms)) {
478 $query = db_insert('taxonomy_term_synonym')
479 ->fields(array('tid', 'name'));
480 foreach (explode ("\n", str_replace("\r", '', $term->synonyms)) as $synonym) {
481 if ($synonym) {
482 $query->values(array(
483 'tid' => $term->tid,
484 'name' => rtrim($synonym)
485 ));
486 }
487 }
488 $query->execute();
489 }
490
491 cache_clear_all();
492 taxonomy_terms_static_reset();
493
494 return $status;
495 }
496
497 /**
498 * Delete a term.
499 *
500 * @param $tid
501 * The term ID.
502 * @return
503 * Status constant indicating deletion.
504 */
505 function taxonomy_term_delete($tid) {
506 $tids = array($tid);
507 while ($tids) {
508 $children_tids = $orphans = array();
509 foreach ($tids as $tid) {
510 // See if any of the term's children are about to be become orphans:
511 if ($children = taxonomy_get_children($tid)) {
512 foreach ($children as $child) {
513 // If the term has multiple parents, we don't delete it.
514 $parents = taxonomy_get_parents($child->tid);
515 if (count($parents) == 1) {
516 $orphans[] = $child->tid;
517 }
518 }
519 }
520
521 $term = taxonomy_term_load($tid);
522
523 db_delete('taxonomy_term_data')
524 ->condition('tid', $tid)
525 ->execute();
526 db_delete('taxonomy_term_hierarchy')
527 ->condition('tid', $tid)
528 ->execute();
529 db_delete('taxonomy_term_synonym')
530 ->condition('tid', $tid)
531 ->execute();
532 db_delete('taxonomy_term_node')
533 ->condition('tid', $tid)
534 ->execute();
535
536 field_attach_delete('taxonomy_term', $term);
537 _taxonomy_clean_field_cache($term);
538 module_invoke_all('taxonomy_term_delete', $term);
539 }
540
541 $tids = $orphans;
542 }
543
544 cache_clear_all();
545 taxonomy_terms_static_reset();
546
547 return SAVED_DELETED;
548 }
549
550 /**
551 * Clear all static cache variables for terms..
552 */
553 function taxonomy_terms_static_reset() {
554 drupal_static_reset('taxonomy_term_count_nodes');
555 drupal_static_reset('taxonomy_get_tree');
556 drupal_static_reset('taxonomy_get_synonym_root');
557 drupal_static_reset('taxonomy_term_load_multiple');
558 }
559
560 /**
561 * Generate a form element for selecting terms from a vocabulary.
562 *
563 * @param $vid
564 * The vocabulary ID to generate a form element for
565 * @param $value
566 * The existing value of the term(s) in this vocabulary to use by default.
567 * @param $help
568 * Optional help text to use for the form element. If specified, this value
569 * MUST be properly sanitized and filtered (e.g. with filter_xss_admin() or
570 * check_plain() if it is user-supplied) to prevent XSS vulnerabilities. If
571 * omitted, the help text stored with the vocaulary (if any) will be used.
572 * @return
573 * An array describing a form element to select terms for a vocabulary.
574 *
575 * @see _taxonomy_term_select()
576 * @see filter_xss_admin()
577 */
578 function taxonomy_form($vid, $value = 0, $help = NULL) {
579 $vocabulary = taxonomy_vocabulary_load($vid);
580 $help = ($help) ? $help : filter_xss_admin($vocabulary->help);
581
582 if (!$vocabulary->multiple) {
583 $blank = ($vocabulary->required) ? t('- Please choose -') : t('- None selected -');
584 }
585 else {
586 $blank = ($vocabulary->required) ? 0 : t('- None -');
587 }
588
589 return _taxonomy_term_select(check_plain($vocabulary->name), $value, $vid, $help, intval($vocabulary->multiple), $blank);
590 }
591
592 /**
593 * Generate a set of options for selecting a term from all vocabularies.
594 */
595 function taxonomy_form_all($free_tags = 0) {
596 $vocabularies = taxonomy_get_vocabularies();
597 $options = array();
598 foreach ($vocabularies as $vid => $vocabulary) {
599 if ($vocabulary->tags && !$free_tags) {
600 continue;
601 }
602 $tree = taxonomy_get_tree($vid);
603 if ($tree && (count($tree) > 0)) {
604 $options[$vocabulary->name] = array();
605 foreach ($tree as $term) {
606 $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name;
607 }
608 }
609 }
610 return $options;
611 }
612
613 /**
614 * Return an array of all vocabulary objects.
615 *
616 * @param $type
617 * If set, return only those vocabularies associated with this node type.
618 */
619 function taxonomy_get_vocabularies($type = NULL) {
620 $conditions = !empty($type) ? array('type' => $type) : NULL;
621 return taxonomy_vocabulary_load_multiple(array(), $conditions);
622 }
623
624 /**
625 * Get names for all taxonomy vocabularies.
626 *
627 * @return
628 * An array of vocabulary names in the format 'machine_name' => 'name'.
629 */
630 function taxonomy_vocabulary_get_names() {
631 $names = array();
632 $vocabularies = taxonomy_get_vocabularies();
633 foreach ($vocabularies as $vocabulary) {
634 $names[$vocabulary->machine_name] = $vocabulary->name;
635 }
636 return $names;
637 }
638
639 /**
640 * Implement hook_form_alter().
641 * Generate a form for selecting terms to associate with a node.
642 * We check for taxonomy_override_selector before loading the full
643 * vocabulary, so contrib modules can intercept before hook_form_alter
644 * and provide scalable alternatives.
645 */
646 function taxonomy_form_alter(&$form, $form_state, $form_id) {
647 if (!variable_get('taxonomy_override_selector', FALSE) && !empty($form['#node_edit_form'])) {
648 $node = $form['#node'];
649
650 if (!isset($node->taxonomy)) {
651 $terms = empty($node->nid) ? array() : taxonomy_node_get_terms($node);
652 }
653 else {
654 // After preview the terms must be converted to objects.
655 if (isset($form_state['node_preview'])) {
656 $node->taxonomy = taxonomy_preview_terms($node);
657 }
658 $terms = $node->taxonomy;
659 }
660 $query = db_select('taxonomy_vocabulary', 'v');
661 $query->join('taxonomy_vocabulary_node_type', 'n', 'v.vid = n.vid');
662 $query->addTag('term_access');
663
664 $result = $query
665 ->fields('v')
666 ->condition('n.type', $node->type)
667 ->orderBy('v.weight')
668 ->orderBy('v.name')
669 ->execute();
670
671 foreach ($result as $vocabulary) {
672 if ($vocabulary->tags) {
673 if (isset($form_state['node_preview'])) {
674 // Typed string can be changed by the user before preview,
675 // so we just insert the tags directly as provided in the form.
676 $typed_string = $node->taxonomy['tags'][$vocabulary->vid];
677 }
678 else {
679 $typed_string = taxonomy_implode_tags($terms, $vocabulary->vid) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
680 }
681 if ($vocabulary->help) {
682 $help = filter_xss_admin($vocabulary->help);
683 }
684 else {
685 $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc."');
686 }
687 $form['taxonomy']['tags'][$vocabulary->vid] = array('#type' => 'textfield',
688 '#title' => $vocabulary->name,
689 '#description' => $help,
690 '#required' => $vocabulary->required,
691 '#default_value' => $typed_string,
692 '#autocomplete_path' => 'taxonomy/autocomplete/' . $vocabulary->vid,
693 '#weight' => $vocabulary->weight,
694 '#maxlength' => 1024,
695 );
696 }
697 else {
698 // Extract terms belonging to the vocabulary in question.
699 $default_terms = array();
700 foreach ($terms as $term) {
701 // Free tagging has no default terms and also no vid after preview.
702 if (isset($term->vid) && $term->vid == $vocabulary->vid) {
703 $default_terms[$term->tid] = $term;
704 }
705 }
706 $form['taxonomy'][$vocabulary->vid] = taxonomy_form($vocabulary->vid, array_keys($default_terms), filter_xss_admin($vocabulary->help));
707 $form['taxonomy'][$vocabulary->vid]['#weight'] = $vocabulary->weight;
708 $form['taxonomy'][$vocabulary->vid]['#required'] = $vocabulary->required;
709 }
710 }
711 if (!empty($form['taxonomy']) && is_array($form['taxonomy'])) {
712 if (count($form['taxonomy']) > 1) {
713 // Add fieldset only if form has more than 1 element.
714 $form['taxonomy'] += array(
715 '#type' => 'fieldset',
716 '#title' => t('Vocabularies'),
717 '#collapsible' => TRUE,
718 '#collapsed' => FALSE,
719 );
720 }
721 $form['taxonomy']['#weight'] = -3;
722 $form['taxonomy']['#tree'] = TRUE;
723 }
724 }
725 }
726
727 /**
728 * Helper function to convert terms after a preview.
729 *
730 * After preview the tags are an array instead of proper objects. This function
731 * converts them back to objects with the exception of 'free tagging' terms,
732 * because new tags can be added by the user before preview and those do not
733 * yet exist in the database. We therefore save those tags as a string so
734 * we can fill the form again after the preview.
735 */
736 function taxonomy_preview_terms($node) {
737 $taxonomy = array();
738 if (isset($node->taxonomy)) {
739 foreach ($node->taxonomy as $key => $term) {
740 unset($node->taxonomy[$key]);
741 // A 'Multiple select' and a 'Free tagging' field returns an array.
742 if (is_array($term)) {
743 foreach ($term as $tid) {
744 if ($key == 'tags') {
745 // Free tagging; the values will be saved for later as strings
746 // instead of objects to fill the form again.
747 $taxonomy['tags'] = $term;
748 }
749 else {
750 $taxonomy[$tid] = taxonomy_term_load($tid);
751 }
752 }
753 }
754 // A 'Single select' field returns the term id.
755 elseif ($term) {
756 $taxonomy[$term] = taxonomy_term_load($term);
757 }
758 }
759 }
760 return $taxonomy;
761 }
762
763 /**
764 * Find all terms associated with the given node, within one vocabulary.
765 */
766 function taxonomy_node_get_terms_by_vocabulary($node, $vid, $key = 'tid') {
767 $query = db_select('taxonomy_term_data', 't');
768 $query->join('taxonomy_term_node', 'r', 'r.tid = t.tid');
769 $query->addTag('term_access');
770
771 $result = $query
772 ->fields('t')
773 ->condition('t.vid', $vid)
774 ->condition('r.vid', $node->vid)
775 ->orderBy('weight')
776 ->execute();
777
778 $terms = array();
779 foreach ($result as $term) {
780 $terms[$term->$key] = $term;
781 }
782 return $terms;
783 }
784
785 /**
786 * Find all term IDs associated with a set of nodes.
787 *
788 * @param $nodes
789 * An array of node objects.
790 *
791 * @return
792 * An array of term and node IDs ordered by vocabulary and term weight.
793 */
794 function taxonomy_get_tids_from_nodes($nodes) {
795 $node_vids = array();
796 foreach ($nodes as $node) {
797 $node_vids[] = $node->vid;
798 }
799 $query = db_select('taxonomy_term_node', 'r');
800 $query->join('taxonomy_term_data', 't', 'r.tid = t.tid');
801 $query->join('taxonomy_vocabulary', 'v', 't.vid = v.vid');
802 $query->addTag('term_access');
803
804 return $query
805 ->fields('r', array('tid', 'nid', 'vid'))
806 ->condition('r.vid', $node_vids, 'IN')
807 ->orderBy('v.weight')
808 ->orderBy('t.weight')
809 ->orderBy('t.name')
810 ->execute()
811 ->fetchAll();
812 }
813
814 /**
815 * Find all terms associated with the given node, ordered by vocabulary and term weight.
816 */
817 function taxonomy_node_get_terms($node, $key = 'tid') {
818 $terms = &drupal_static(__FUNCTION__);
819
820 if (!isset($terms[$node->vid][$key])) {
821 $query = db_select('taxonomy_term_node', 'r');
822 $query->join('taxonomy_term_data', 't', 'r.tid = t.tid');
823 $query->join('taxonomy_vocabulary', 'v', 't.vid = v.vid');
824 $query->addTag('term_access');
825
826 $result = $query
827 ->fields('r', array('tid', 'nid', 'vid'))
828 ->condition('r.vid', $node->vid)
829 ->orderBy('v.weight')
830 ->orderBy('t.weight')
831 ->orderBy('t.name')
832 ->execute();
833 $terms[$node->vid][$key] = array();
834 foreach ($result as $term) {
835 $terms[$node->vid][$key][$term->$key] = $term;
836 }
837 }
838 return $terms[$node->vid][$key];
839 }
840
841 /**
842 * Save term associations for a given node.
843 */
844 function taxonomy_node_save($node, $terms) {
845
846 taxonomy_node_delete_revision($node);
847
848 // Free tagging vocabularies do not send their tids in the form,
849 // so we'll detect them here and process them independently.
850 if (isset($terms['tags'])) {
851 $typed_input = $terms['tags'];
852 unset($terms['tags']);
853
854 foreach ($typed_input as $vid => $vid_value) {
855 $vocabulary = taxonomy_vocabulary_load($vid);
856 $typed_terms = drupal_explode_tags($vid_value);
857
858 $inserted = array();
859 foreach ($typed_terms as $typed_term) {
860 // See if the term exists in the chosen vocabulary
861 // and return the tid; otherwise, add a new record.
862 $possibilities = taxonomy_get_term_by_name($typed_term);
863 $typed_term_tid = NULL; // tid match, if any.
864 foreach ($possibilities as $possibility) {
865 if ($possibility->vid == $vid) {
866 $typed_term_tid = $possibility->tid;
867 }
868 }
869
870 if (!$typed_term_tid) {
871 $edit = array(
872 'vid' => $vid,
873 'name' => $typed_term,
874 'vocabulary_machine_name' => $vocabulary->machine_name,
875 );
876 $term = (object)$edit;
877 $status = taxonomy_term_save($term);
878 $typed_term_tid = $term->tid;
879 }
880
881 // Defend against duplicate, differently cased tags
882 if (!isset($inserted[$typed_term_tid])) {
883 db_insert('taxonomy_term_node')
884 ->fields(array(
885 'nid' => $node->nid,
886 'vid' => $node->vid,
887 'tid' => $typed_term_tid
888 ))
889 ->execute();
890 $inserted[$typed_term_tid] = TRUE;
891 }
892 }
893 }
894 }
895
896 if (is_array($terms) && !empty($terms)) {
897 $query = db_insert('taxonomy_term_node')
898 ->fields(array('nid', 'vid', 'tid'));
899
900 foreach ($terms as $term) {
901 if (is_array($term)) {
902 foreach ($term as $tid) {
903 if ($tid) {
904 $query->values(array(
905 'nid' => $node->nid,
906 'vid' => $node->vid,
907 'tid' => $tid,
908 ));
909 }
910 }
911 }
912 elseif (is_object($term)) {
913 $query->values(array(
914 'nid' => $node->nid,
915 'vid' => $node->vid,
916 'tid' => $term->tid,
917 ));
918 }
919 elseif ($term) {
920 $query->values(array(
921 'nid' => $node->nid,
922 'vid' => $node->vid,
923 'tid' => $term,
924 ));
925 }
926 }
927 $query->execute();
928 }
929 }
930
931 /**
932 * Implement hook_node_type_insert().
933 */
934 function taxonomy_node_type_insert($info) {
935 drupal_static_reset('taxonomy_term_count_nodes');
936 }
937
938 /**
939 * Implement hook_node_type_update().
940 */
941 function taxonomy_node_type_update($info) {
942 if (!empty($info->old_type) && $info->type != $info->old_type) {
943 db_update('taxonomy_vocabulary_node_type')
944 ->fields(array(
945 'type' => $info->type,
946 ))
947 ->condition('type', $info->old_type)
948 ->execute();
949 }
950 drupal_static_reset('taxonomy_term_count_nodes');
951 }
952
953 /**
954 * Implement hook_node_type_delete().
955 */
956 function taxonomy_node_type_delete($info) {
957 db_delete('taxonomy_vocabulary_node_type')
958 ->condition('type', $info->type)
959 ->execute();
960
961 drupal_static_reset('taxonomy_term_count_nodes');
962 }
963
964 /**
965 * Find all parents of a given term ID.
966 */
967 function taxonomy_get_parents($tid, $key = 'tid') {
968 if ($tid) {
969 $query = db_select('taxonomy_term_data', 't');
970 $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid');
971 $query->addTag('term_access');
972
973 $result = $query
974 ->fields('t')
975 ->condition('h.tid', $tid)
976 ->orderBy('weight')
977 ->orderBy('name')
978 ->execute();
979 $parents = array();
980 foreach ($result as $parent) {
981 $parents[$parent->$key] = $parent;
982 }
983 return $parents;
984 }
985 else {
986 return array();
987 }
988 }
989
990 /**
991 * Find all ancestors of a given term ID.
992 */
993 function taxonomy_get_parents_all($tid) {
994 $parents = array();
995 if ($term = taxonomy_term_load($tid)) {
996 $parents[] = $term;
997 $n = 0;
998 while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
999 $parents = array_merge($parents, $parent);
1000 $n++;
1001 }
1002 }
1003 return $parents;
1004 }
1005
1006 /**
1007 * Find all children of a term ID.
1008 */
1009 function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
1010 $query = db_select('taxonomy_term_data', 't');
1011 $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
1012 $query->addTag('term_access');
1013
1014 $query
1015 ->fields('t')
1016 ->condition('parent', $tid)
1017 ->orderBy('weight')
1018 ->orderBy('name');
1019 if ($vid) {
1020 $query->condition('t.vid', $vid);
1021 }
1022 $result = $query->execute();
1023
1024 $children = array();
1025 foreach ($result as $term) {
1026 $children[$term->$key] = $term;
1027 }
1028 return $children;
1029 }
1030
1031 /**
1032 * Create a hierarchical representation of a vocabulary.
1033 *
1034 * @param $vid
1035 * Which vocabulary to generate the tree for.
1036 * @param $parent
1037 * The term ID under which to generate the tree. If 0, generate the tree
1038 * for the entire vocabulary.
1039 * @param $max_depth
1040 * The number of levels of the tree to return. Leave NULL to return all levels.
1041 * @param $depth
1042 * Internal use only.
1043 *
1044 * @return
1045 * An array of all term objects in the tree. Each term object is extended
1046 * to have "depth" and "parents" attributes in addition to its normal ones.
1047 * Results are statically cached.
1048 */
1049 function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $depth = -1) {
1050 $children = &drupal_static(__FUNCTION__, array());
1051 $parents = &drupal_static(__FUNCTION__ . 'parents', array());
1052 $terms = &drupal_static(__FUNCTION__ . 'terms', array());
1053
1054 $depth++;
1055
1056 // We cache trees, so it's not CPU-intensive to call get_tree() on a term
1057 // and its children, too.
1058 if (!isset($children[$vid])) {
1059 $children[$vid] = array();
1060 $parents[$vid] = array();
1061 $terms[$vid] = array();
1062
1063 $query = db_select('taxonomy_term_data', 't');
1064 $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
1065 $query->addTag('term_access');
1066
1067 $result = $query
1068 ->fields('t')
1069 ->fields('h', array('parent'))
1070 ->condition('t.vid', $vid)
1071 ->orderBy('weight')
1072 ->orderBy('name')
1073 ->execute();
1074 foreach ($result as $term) {
1075 $children[$vid][$term->parent][] = $term->tid;
1076 $parents[$vid][$term->tid][] = $term->parent;
1077 $terms[$vid][$term->tid] = $term;
1078 }
1079 }
1080
1081 $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
1082 $tree = array();
1083 if ($max_depth > $depth && !empty($children[$vid][$parent])) {
1084 foreach ($children[$vid][$parent] as $child) {
1085 $term = clone $terms[$vid][$child];
1086 $term->depth = $depth;
1087 // The "parent" attribute is not useful, as it would show one parent only.
1088 unset($term->parent);
1089 $term->parents = $parents[$vid][$child];
1090 $tree[] = $term;
1091 if (!empty($children[$vid][$child])) {
1092 $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $max_depth, $depth));
1093 }
1094 }
1095 }
1096
1097 return $tree;
1098 }
1099
1100 /**
1101 * Return an array of synonyms of the given term ID.
1102 */
1103 function taxonomy_get_synonyms($tid) {
1104 if ($tid) {
1105 $synonyms = array();
1106 return db_query('SELECT name FROM {taxonomy_term_synonym} WHERE tid = :tid', array(':tid' => $tid))->fetchCol();
1107 }
1108 else {
1109 return array();
1110 }
1111 }
1112
1113 /**
1114 * Return the term object that has the given string as a synonym.
1115 *
1116 * @param $synonym
1117 * The string to compare against.
1118 * @return
1119 * A term object, or FALSE if no matching term is found.
1120 */
1121 function taxonomy_get_synonym_root($synonym) {
1122 $synonyms = &drupal_static(__FUNCTION__, array());
1123
1124 if (!isset($synonyms[$synonym])) {
1125 $synonyms[$synonym] = db_query("SELECT * FROM {taxonomy_term_synonym} s, {taxonomy_term_data} t WHERE t.tid = s.tid AND s.name = :name", array(':name' => $synonym))->fetch();
1126 }
1127 return $synonyms[$synonym];
1128 }
1129
1130 /**
1131 * Count the number of published nodes classified by a term.
1132 *
1133 * @param $tid
1134 * The term ID
1135 * @param $type
1136 * (Optional) The $node->type. If given, taxonomy_term_count_nodes only counts
1137 * nodes of $type that are classified with the term $tid.
1138 *
1139 * @return
1140 * An integer representing a number of nodes.
1141 * Results are statically cached.
1142 */
1143 function taxonomy_term_count_nodes($tid, $type = NULL) {
1144 $count = &drupal_static(__FUNCTION__, array());
1145 // Reset the taxonomy tree when first called (or if reset).
1146 if (empty($count)) {
1147 drupal_static_reset('taxonomy_get_tree');
1148 }
1149 // If $type is NULL, change it to 0 to allow it to be used as an array key
1150 // for the static cache.
1151 $type = empty($type) ? 0 : $type;
1152
1153 if (!isset($count[$type][$tid])) {
1154 $term = taxonomy_term_load($tid);
1155 $tree = taxonomy_get_tree($term->vid, $tid, NULL);
1156 $tids = array($tid);
1157 foreach ($tree as $descendent) {
1158 $tids[] = $descendent->tid;
1159 }
1160
1161 $query = db_select('taxonomy_term_node', 't');
1162 $query->addExpression('COUNT(DISTINCT(n.nid))', 'nid_count');
1163 $query->join('node', 'n', 't.vid = n.vid');
1164 $query->condition('t.tid', $tids, 'IN');
1165 $query->condition('n.status', 1);
1166 if (!is_numeric($type)) {
1167 $query->condition('n.type', $type);
1168 }
1169 $query->addTag('term_access');
1170 $count[$type][$tid] = $query->execute()->fetchField();
1171 }
1172 return $count[$type][$tid];
1173 }
1174
1175 /**
1176 * Try to map a string to an existing term, as for glossary use.
1177 *
1178 * Provides a case-insensitive and trimmed mapping, to maximize the
1179 * likelihood of a successful match.
1180 *
1181 * @param name
1182 * Name of the term to search for.
1183 *
1184 * @return
1185 * An array of matching term objects.
1186 */
1187 function taxonomy_get_term_by_name($name) {
1188 return taxonomy_term_load_multiple(array(), array('name' => trim($name)));
1189 }
1190
1191 /**
1192 * Load multiple taxonomy vocabularies based on certain conditions.
1193 *
1194 * This function should be used whenever you need to load more than one
1195 * vocabulary from the database. Terms are loaded into memory and will not
1196 * require database access if loaded again during the same page request.
1197 *
1198 * @param $vids
1199 * An array of taxonomy vocabulary IDs.
1200 * @param $conditions
1201 * An array of conditions to add to the query.
1202 *
1203 * @return
1204 * An array of vocabulary objects, indexed by vid.
1205 */
1206 function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array()) {
1207 $vocabulary_cache = &drupal_static(__FUNCTION__, array());
1208 // Node type associations are not stored in the vocabulary table, so remove
1209 // this from conditions into it's own variable.
1210 if (isset($conditions['type'])) {
1211 $type = $conditions['type'];
1212 unset($conditions['type']);
1213 }
1214
1215 $vocabularies = array();
1216
1217 // Create a new variable which is either a prepared version of the $vids
1218 // array for later comparison with the term cache, or FALSE if no $vids were
1219 // passed. The $vids array is reduced as items are loaded from cache, and we
1220 // need to know if it's empty for this reason to avoid querying the database
1221 // when all requested items are loaded from cache.
1222 $passed_vids = !empty($vids) ? array_flip($vids) : FALSE;
1223
1224 // Load any available items from the internal cache.
1225 if ($vocabulary_cache) {
1226 if ($vids) {
1227 $vocabularies += array_intersect_key($vocabulary_cache, $passed_vids);
1228 // If any items were loaded, remove them from the $vids still to load.
1229 $vids = array_keys(array_diff_key($passed_vids, $vocabularies));
1230 }
1231 // If only conditions is passed, load all items from the cache. Items
1232 // which don't match conditions will be removed later.
1233 elseif ($conditions) {
1234 $vocabularies = $vocabulary_cache;
1235 }
1236 }
1237
1238 // Remove any loaded terms from the array if they don't match $conditions.
1239 if ($conditions || isset($type)) {
1240 foreach ($vocabularies as $vocabulary) {
1241 $vocabulary_values = (array) $vocabulary;
1242 if (array_diff_assoc($conditions, $vocabulary_values)) {
1243 unset($vocabularies[$vocabulary->vid]);
1244 }
1245 if (isset($type) && !in_array($type, $vocabulary->nodes)) {
1246 unset($vocabularies[$vocabulary->vid]);
1247 }
1248 }
1249 }
1250
1251 // Load any remaining vocabularies from the database, this is necessary if
1252 // we have $vids still to load, or if no $vids were passed.
1253 if ($vids || !$passed_vids) {
1254 $query = db_select('taxonomy_vocabulary', 'v');
1255 $query->addField('n', 'type');
1256 $query
1257 ->fields('v')
1258 ->orderBy('v.weight')
1259 ->orderBy('v.name')
1260 ->addTag('vocabulary_access');
1261
1262 if (!empty($type)) {
1263 $query->join('taxonomy_vocabulary_node_type', 'n', 'v.vid = n.vid AND n.type = :type', array(':type' => $type));
1264 }
1265 else {
1266 $query->leftJoin('taxonomy_vocabulary_node_type', 'n', 'v.vid = n.vid');
1267 }
1268
1269 // If the $vids array is populated, add those to the query.
1270 if ($vids) {
1271 $query->condition('v.vid', $vids, 'IN');
1272 }
1273
1274 // If the conditions array is populated, add those to the query.
1275 if ($conditions) {
1276 foreach ($conditions as $field => $value) {
1277 $query->condition('v.' . $field, $value);
1278 }
1279 }
1280 $result = $query->execute();
1281
1282 $queried_vocabularies = array();
1283 $node_types = array();
1284 foreach ($result as $record) {
1285 // If no node types are associated with a vocabulary, the LEFT JOIN will
1286 // return a NULL value for type.
1287 if (isset($record->type)) {
1288 $node_types[$record->vid][$record->type] = $record->type;
1289 unset($record->type);
1290 $record->nodes = $node_types[$record->vid];
1291 }
1292 elseif (!isset($record->nodes)) {
1293 $record->nodes = array();
1294 }
1295 $queried_vocabularies[$record->vid] = $record;
1296 }
1297
1298 // Invoke hook_taxonomy_vocabulary_load() on the vocabularies loaded from
1299 // the database and add them to the static cache.
1300 if (!empty($queried_vocabularies)) {
1301 foreach (module_implements('taxonomy_vocabulary_load') as $module) {
1302 $function = $module . '_taxonomy_vocabulary_load';
1303 $function($queried_vocabularies);
1304 }
1305 $vocabularies += $queried_vocabularies;
1306 $vocabulary_cache += $queried_vocabularies;
1307 }
1308 }
1309
1310 // Ensure that the returned array is ordered the same as the original $vids
1311 // array if this was passed in and remove any invalid vids.
1312 if ($passed_vids) {
1313 // Remove any invalid vids from the array.
1314 $passed_vids = array_intersect_key($passed_vids, $vocabularies);
1315 foreach ($vocabularies as $vocabulary) {
1316 $passed_vids[$vocabulary->vid] = $vocabulary;
1317 }
1318 $vocabularies = $passed_vids;
1319 }
1320
1321 return $vocabularies;
1322 }
1323
1324 /**
1325 * Return the vocabulary object matching a vocabulary ID.
1326 *
1327 * @param $vid
1328 * The vocabulary's ID.
1329 *
1330 * @return
1331 * The vocabulary object with all of its metadata, if exists, FALSE otherwise.
1332 * Results are statically cached.
1333 */
1334 function taxonomy_vocabulary_load($vid) {
1335 return reset(taxonomy_vocabulary_load_multiple(array($vid), array()));
1336 }
1337
1338 /**
1339 * Load multiple taxonomy terms based on certain conditions.
1340 *
1341 * This function should be used whenever you need to load more than one term
1342 * from the database. Terms are loaded into memory and will not require
1343 * database access if loaded again during the same page request.
1344 *
1345 * @param $tids
1346 * An array of taxonomy term IDs.
1347 * @param $conditions
1348 * An array of conditions to add to the query.
1349 *
1350 * @return
1351 * An array of term objects, indexed by tid.
1352 */
1353 function taxonomy_term_load_multiple($tids = array(), $conditions = array()) {
1354 $term_cache = &drupal_static(__FUNCTION__, array());
1355
1356 // Node type associations are not stored in the taxonomy_term_data table, so
1357 // remove this from conditions into it's own variable.
1358 if (isset($conditions['type'])) {
1359 $type = $conditions['type'];
1360 unset($conditions['type']);
1361 }
1362
1363 $terms = array();
1364
1365 // Create a new variable which is either a prepared version of the $tids
1366 // array for later comparison with the term cache, or FALSE if no $tids were
1367 // passed. The $tids array is reduced as items are loaded from cache, and we
1368 // need to know if it's empty for this reason to avoid querying the database
1369 // when all requested terms are loaded from cache.
1370 $passed_tids = !empty($tids) ? array_flip($tids) : FALSE;
1371
1372 // Load any available terms from the internal cache.
1373 if ($term_cache) {
1374 if ($tids) {
1375 $terms += array_intersect_key($term_cache, $passed_tids);
1376 // If any terms were loaded, remove them from the $tids still to load.
1377 $tids = array_keys(array_diff_key($passed_tids, $terms));
1378 }
1379 // If only conditions is passed, load all terms from the cache. Terms
1380 // which don't match conditions will be removed later.
1381 elseif ($conditions) {
1382 $terms = $term_cache;
1383 }
1384 }
1385
1386 // Remove any loaded terms from the array if they don't match $conditions.
1387 if ($conditions) {
1388 // Name matching is case insensitive, note that with some collations
1389 // LOWER() and drupal_strtolower() may return different results.
1390 foreach ($terms as $term) {
1391 $term_values = (array) $term;
1392 if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) {
1393 unset($terms[$term->tid]);
1394 }
1395 elseif (array_diff_assoc($conditions, $term_values)) {
1396 unset($terms[$term->tid]);
1397 }
1398 }
1399 }
1400
1401 // Load any remaining terms from the database, this is necessary if we have
1402 // $tids still to load, or if $conditions was passed without $tids.
1403 if ($tids || ($conditions && !$passed_tids)) {
1404 $query = db_select('taxonomy_term_data', 't');
1405 $query->addTag('term_access');
1406 $query->join('taxonomy_vocabulary', 'v', 't.vid = v.vid');
1407 $taxonomy_term_data = drupal_schema_fields_sql('taxonomy_term_data');
1408 $query->addField('v', 'machine_name', 'vocabulary_machine_name');
1409 $query
1410 ->fields('t', $taxonomy_term_data)
1411 ->addTag('term_access');
1412
1413 // If the $tids array is populated, add those to the query.
1414 if ($tids) {
1415 $query->condition('t.tid', $tids, 'IN');
1416 }
1417
1418 if (!empty($type)) {
1419 $query->join('taxonomy_vocabulary_node_type', 'n', 't.vid = n.vid AND n.type = :type', array(':type' => $type));
1420 }
1421
1422 // If the conditions array is populated, add those to the query.
1423 if ($conditions) {
1424 // When name is passed as a condition use LIKE.
1425 if (isset($conditions['name'])) {
1426 $query->condition('t.name', $conditions['name'], 'LIKE');
1427 unset($conditions['name']);
1428 }
1429 foreach ($conditions as $field => $value) {
1430 $query->condition('t.' . $field, $value);
1431 }
1432 }
1433 $queried_terms = $query->execute()->fetchAllAssoc('tid');
1434
1435 if (!empty($queried_terms)) {
1436
1437 // Attach fields.
1438 field_attach_load('taxonomy_term', $queried_terms);
1439
1440 // Invoke hook_taxonomy_term_load() and add the term objects to the
1441 // static cache.
1442 foreach (module_implements('taxonomy_term_load') as $module) {
1443 $function = $module . '_taxonomy_term_load';
1444 $function($queried_terms);
1445 }
1446 $terms += $queried_terms;
1447 $term_cache += $queried_terms;
1448 }
1449 }
1450
1451 // Ensure that the returned array is ordered the same as the original $tids
1452 // array if this was passed in and remove any invalid tids.
1453 if ($passed_tids) {
1454 // Remove any invalid tids from the array.
1455 $passed_tids = array_intersect_key($passed_tids, $terms);
1456 foreach ($terms as $term) {
1457 $passed_tids[$term->tid] = $term;
1458 }
1459 $terms = $passed_tids;
1460 }
1461
1462 return $terms;
1463 }
1464
1465 /**
1466 * Return the term object matching a term ID.
1467 *
1468 * @param $tid
1469 * A term's ID
1470 *
1471 * @return
1472 * A term object. Results are statically cached.
1473 */
1474 function taxonomy_term_load($tid) {
1475 if (!is_numeric($tid)) {
1476 return FALSE;
1477 }
1478 $term = taxonomy_term_load_multiple(array($tid), array());
1479 return $term ? $term[$tid] : FALSE;
1480 }
1481
1482 /**
1483 * Create a select form element for a given taxonomy vocabulary.
1484 *
1485 * NOTE: This function expects input that has already been sanitized and is
1486 * safe for display. Callers must properly sanitize the $title and
1487 * $description arguments to prevent XSS vulnerabilities.
1488 *
1489 * @param $title
1490 * The title of the vocabulary. This MUST be sanitized by the caller.
1491 * @param $value
1492 * The currently selected terms from this vocabulary, if any.
1493 * @param $vocabulary_id
1494 * The vocabulary ID to build the form element for.
1495 * @param $description
1496 * Help text for the form element. This MUST be sanitized by the caller.
1497 * @param $multiple
1498 * Boolean to control if the form should use a single or multiple select.
1499 * @param $blank
1500 * Optional form choice to use when no value has been selected.
1501 * @param $exclude
1502 * Optional array of term ids to exclude in the selector.
1503 * @return
1504 * A FAPI form array to select terms from the given vocabulary.
1505 *
1506 * @see taxonomy_form()
1507 * @see taxonomy_form_term()
1508 */
1509 function _taxonomy_term_select($title, $value, $vocabulary_id, $description, $multiple, $blank, $exclude = array()) {
1510 $tree = taxonomy_get_tree($vocabulary_id);
1511 $options = array();
1512
1513 if ($blank) {
1514 $options[0] = $blank;
1515 }
1516 if ($tree) {
1517 foreach ($tree as $term) {
1518 if (!in_array($term->tid, $exclude)) {
1519 $choice = new stdClass();
1520 $choice->option = array($term->tid => str_repeat('-', $term->depth) . $term->name);
1521 $options[] = $choice;
1522 }
1523 }
1524 }
1525
1526 return array('#type' => 'select',
1527 '#title' => $title,
1528 '#default_value' => $value,
1529 '#options' => $options,
1530 '#description' => $description,
1531 '#multiple' => $multiple,
1532 '#size' => $multiple ? min(9, count($options)) : 0,
1533 '#weight' => -15,
1534 '#theme' => 'taxonomy_term_select',
1535 );
1536 }
1537
1538 /**
1539 * Format the selection field for choosing terms
1540 * (by default the default selection field is used).
1541 *
1542 * @ingroup themeable
1543 */
1544 function theme_taxonomy_term_select($element) {
1545 return theme('select', $element);
1546 }
1547
1548 /**
1549 * Finds all nodes that match selected taxonomy conditions.
1550 *
1551 * @param $tids
1552 * An array of term IDs to match.
1553 * @param $operator
1554 * How to interpret multiple IDs in the array. Can be "or" or "and".
1555 * @param $depth
1556 * How many levels deep to traverse the taxonomy tree. Can be a non-negative
1557 * integer or "all".
1558 * @param $pager
1559 * Whether the nodes are to be used with a pager (the case on most Drupal
1560 * pages) or not (in an XML feed, for example).
1561 * @param $order
1562 * The order clause for the query that retrieve the nodes.
1563 * It is important to specifc the table alias (n).
1564 *
1565 * Example:
1566 * array('table_alias.field_name' = 'DESC');
1567 * @return
1568 * An array of node IDs.
1569 */
1570 function taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE, $order = array('n.sticky' => 'DESC', 'n.created' => 'DESC')) {
1571 if (count($tids) <= 0) {
1572 return array();
1573 }
1574 // For each term ID, generate an array of descendant term IDs to the right depth.
1575 $descendant_tids = array();
1576 if ($depth === 'all') {
1577 $depth = NULL;
1578 }
1579 $terms = taxonomy_term_load_multiple($tids);
1580 foreach ($terms as $term) {
1581 $tree = taxonomy_get_tree($term->vid, $term->tid, $depth);
1582 $descendant_tids[] = array_merge(array($term->tid), array_map('_taxonomy_get_tid_from_term', $tree));
1583 }
1584
1585 $query = db_select('node', 'n');
1586 $query->addTag('node_access');
1587 $query->condition('n.status', 1);
1588
1589 if ($operator == 'or') {
1590 $args = call_user_func_array('array_merge', $descendant_tids);
1591 $query->join('taxonomy_term_node', 'tn', 'n.vid = tn.vid');
1592 $query->condition('tn.tid', $args, 'IN');
1593 }
1594 else {
1595 foreach ($descendant_tids as $tids) {
1596 $alias = $query->join('taxonomy_term_node', 'tn', 'n.vid = tn.vid');
1597 $query->condition($alias . '.tid', $tids, 'IN');
1598 }
1599 }
1600
1601 if ($pager) {
1602 $count_query = clone $query;
1603 $count_query->addExpression('COUNT(DISTINCT n.nid)');
1604
1605 $query = $query
1606 ->extend('PagerDefault')
1607 ->limit(variable_get('default_nodes_main', 10));
1608 $query->setCountQuery($count_query);
1609 }
1610 else {
1611 $query->range(0, variable_get('feed_default_items', 10));
1612 }
1613
1614 $query->distinct(TRUE);
1615 $query->addField('n', 'nid');
1616 foreach ($order as $field => $direction) {
1617 $query->orderBy($field, $direction);
1618 // ORDER BY fields need to be loaded too, assume they are in the form table_alias.name
1619 list($table_alias, $name) = explode('.', $field);
1620 $query->addField($table_alias, $name);
1621 }
1622
1623 return $query->execute()->fetchCol();
1624 }
1625
1626 /**
1627 * Implement hook_node_load().
1628 */
1629 function taxonomy_node_load($nodes) {
1630 // Get an array of tid, vid associations ordered by vocabulary and term
1631 // weight.
1632 $tids = taxonomy_get_tids_from_nodes($nodes);
1633
1634 // Extract the tids only from this array.
1635 $term_ids = array();
1636 foreach ($tids as $term) {
1637 $term_ids[$term->tid] = $term->tid;
1638 }
1639
1640 // Load the full term objects for these tids.
1641 $terms = taxonomy_term_load_multiple($term_ids);
1642 foreach ($tids as $term) {
1643 $nodes[$term->nid]->taxonomy[$term->tid] = $terms[$term->tid];
1644 }
1645 foreach ($nodes as $node) {
1646 if (!isset($nodes[$node->nid]->taxonomy)) {
1647 $node->taxonomy = array();
1648 }
1649 }
1650 }
1651
1652 /**
1653 * Implement hook_node_insert().
1654 */
1655 function taxonomy_node_insert($node) {
1656 if (!empty($node->taxonomy)) {
1657 taxonomy_node_save($node, $node->taxonomy);
1658 }
1659 }
1660
1661 /**
1662 * Implement hook_node_update().
1663 */
1664 function taxonomy_node_update($node) {
1665 if (!empty($node->taxonomy)) {
1666 taxonomy_node_save($node, $node->taxonomy);
1667 }
1668 }
1669
1670 /**
1671 * Implement hook_node_delete().
1672 *
1673 * Remove associations of a node to its terms.
1674 */
1675 function taxonomy_node_delete($node) {
1676 db_delete('taxonomy_term_node')
1677 ->condition('nid', $node->nid)
1678 ->execute();
1679 drupal_static_reset('taxonomy_term_count_nodes');
1680 }
1681
1682 /**
1683 * Implement hook_node_delete_revision().
1684 *
1685 * Remove associations of a node to its terms.
1686 */
1687 function taxonomy_node_delete_revision($node) {
1688 db_delete('taxonomy_term_node')
1689 ->condition('vid', $node->vid)
1690 ->execute();
1691 drupal_static_reset('taxonomy_term_count_nodes');
1692 }
1693
1694 /**
1695 * Implement hook_node_validate().
1696 *
1697 * Make sure incoming vids are free tagging enabled.
1698 */
1699 function taxonomy_node_validate($node, $form) {
1700 if (!empty($node->taxonomy)) {
1701 $terms = $node->taxonomy;
1702 if (!empty($terms['tags'])) {
1703 foreach ($terms['tags'] as $vid => $vid_value) {
1704 $vocabulary = taxonomy_vocabulary_load($vid);
1705 if (empty($vocabulary->tags)) {
1706 // see form_get_error $key = implode('][', $element['#parents']);
1707 // on why this is the key
1708 form_set_error("taxonomy][tags][$vid", t('The %name vocabulary can not be modified in this way.', array('%name' => $vocabulary->name)));
1709 }
1710 }
1711 }
1712 }
1713 }
1714
1715 /**
1716 * Implement hook_node_update_index().
1717 */
1718 function taxonomy_node_update_index($node) {
1719 $output = array();
1720 foreach ($node->taxonomy as $term) {
1721 $output[] = $term->name;
1722 }
1723 if (count($output)) {
1724 return '<strong>(' . implode(', ', $output) . ')</strong>';
1725 }
1726 }
1727
1728 /**
1729 * Implement hook_help().
1730 */
1731 function taxonomy_help($path, $arg) {
1732 switch ($path) {
1733 case 'admin/help#taxonomy':
1734 $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>';
1735 $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>';
1736 $output .= '<p>' . t('Type of Meal: <em>Appetizer, Main Course, Salad, Dessert</em>') . '</p>';
1737 $output .= '<p>' . t('Preparation Time: <em>0-30mins, 30-60mins, 1-2 hrs, 2hrs+</em>') . '</p>';
1738 $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>';
1739 $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>';
1740 $output .= '<p>' . t('The taxonomy module supports the use of both synonyms and related terms, but does not directly use this functionality. However, optional contributed or custom modules may make full use of these advanced features.') . '</p>';
1741 $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>';
1742 return $output;
1743 case 'admin/structure/taxonomy':
1744 $output = '<p>' . t("The taxonomy module allows you to categorize your content using both tags and administrator defined terms. It is a flexible tool for classifying content with many advanced features. To begin, create a 'Vocabulary' to hold one set of terms or tags. You can create one free-tagging vocabulary for everything, or separate controlled vocabularies to define the various properties of your content, for example 'Countries' or 'Colors'.") . '</p>';
1745 $output .= '<p>' . t('Use the list below to configure and review the vocabularies defined on your site, or to list and manage the terms (tags) they contain. A vocabulary may (optionally) be tied to specific content types as shown in the <em>Type</em> column and, if so, will be displayed when creating or editing posts of that type. Multiple vocabularies tied to the same content type will be displayed in the order shown below. Remember that your changes will not be saved until you click the <em>Save</em> button at the bottom of the page.') . '</p>';
1746 return $output;
1747 case 'admin/structure/taxonomy/%/list':
1748 $vocabulary = taxonomy_vocabulary_load($arg[3]);
1749 if ($vocabulary->tags) {
1750 return '<p>' . t('%capital_name is a free-tagging vocabulary. To change the name or description of a term, click the <em>edit</em> link next to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name))) . '</p>';
1751 }
1752 switch ($vocabulary->hierarchy) {
1753 case 0:
1754 return '<p>' . t('%capital_name is a flat vocabulary. You may organize the terms in the %name vocabulary by using the handles on the left side of the table. To change the name or description of a term, click the <em>edit</em> link next to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
1755 case 1:
1756 return '<p>' . t('%capital_name is a single hierarchy vocabulary. You may organize the terms in the %name vocabulary by using the handles on the left side of the table. To change the name or description of a term, click the <em>edit</em> link next to the term.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
1757 case 2:
1758 return '<p>' . t('%capital_name is a multiple hierarchy vocabulary. To change the name or description of a term, click the <em>edit</em> link next to the term. Drag and drop of multiple hierarchies 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>';
1759 }
1760 case 'admin/structure/taxonomy/add':
1761 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>';
1762 }
1763 }
1764
1765 /**
1766 * Helper function for array_map purposes.
1767 */
1768 function _taxonomy_get_tid_from_term($term) {
1769 return $term->tid;
1770 }
1771
1772 /**
1773 * Implode a list of tags of a certain vocabulary into a string.
1774 */
1775 function taxonomy_implode_tags($tags, $vid = NULL) {
1776 $typed_tags = array();
1777 foreach ($tags as $tag) {
1778 // Extract terms belonging to the vocabulary in question.
1779 if (is_null($vid) || $tag->vid == $vid) {
1780
1781 // Commas and quotes in tag names are special cases, so encode 'em.
1782 if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
1783 $tag->name = '"' . str_replace('"', '""', $tag->name) . '"';
1784 }
1785
1786 $typed_tags[] = $tag->name;
1787 }
1788 }
1789 return implode(', ', $typed_tags);
1790 }
1791
1792 /**
1793 * Implement hook_hook_info().
1794 */
1795 function taxonomy_hook_info() {
1796 return array(
1797 'taxonomy' => array(
1798 'taxonomy' => array(
1799 'insert' => array(
1800 'runs when' => t('After saving a new term to the database'),
1801 ),
1802 'update' => array(
1803 'runs when' => t('After saving an updated term to the database'),
1804 ),
1805 'delete' => array(
1806 'runs when' => t('After deleting a term')
1807 ),
1808 ),
1809 ),
1810 );
1811 }
1812
1813 /**
1814 * Implement hook_field_info().
1815 *
1816 * Field settings:
1817 * - allowed_values: a list array of one or more vocabulary trees:
1818 * - vid: a vocabulary ID.
1819 * - parent: a term ID of a term whose children are allowed. This should be
1820 * '0' if all terms in a vocabulary are allowed. The allowed values do not
1821 * include the parent term.
1822 *
1823 */
1824 function taxonomy_field_info() {
1825 return array(
1826 'taxonomy_term' => array(
1827 'label' => t('Taxonomy term'),
1828 'description' => t('This field stores a reference to a taxonomy term.'),
1829 'default_widget' => 'options_select',
1830 'default_formatter' => 'taxonomy_term_link',
1831 'settings' => array(
1832 'allowed_values' => array(
1833 array(
1834 'vid' => '0',
1835 'parent' => '0',
1836 ),
1837 ),
1838 ),
1839 ),
1840 );
1841 }
1842
1843 /**
1844 * Implement hook_field_widget_info_alter().
1845 */
1846 function taxonomy_field_widget_info_alter(&$info) {
1847 $info['options_select']['field types'][] = 'taxonomy_term';
1848 $info['options_buttons']['field types'][] = 'taxonomy_term';
1849 }
1850
1851 /**
1852 * Implement hook_field_schema().
1853 */
1854 function taxonomy_field_schema($field) {
1855 return array(
1856 'columns' => array(
1857 'value' => array(
1858 'type' => 'int',
1859 'unsigned' => TRUE,
1860 'not null' => FALSE,
1861 ),
1862 ),
1863 'indexes' => array(
1864 'value' => array('value'),
1865 ),
1866 );
1867 }
1868
1869 /**
1870 * Implement hook_field_validate().
1871 *
1872 * Possible error codes:
1873 * - 'taxonomy_term_illegal_value': The value is not part of the list of allowed values.
1874 */
1875 function taxonomy_field_validate($obj_type, $object, $field, $instance, $items, &$errors) {
1876 $allowed_values = taxonomy_allowed_values($field);
1877 foreach ($items as $delta => $item) {
1878 if (!empty($item['value'])) {
1879 if (!isset($allowed_values[$item['value']])) {
1880 $errors[$field['field_name']][$delta][] = array(
1881 'error' => 'taxonomy_term_illegal_value',
1882 'message' => t('%name: illegal value.', array('%name' => t($instance['label']))),
1883 );
1884 }
1885 }
1886 }
1887 }
1888
1889 /**
1890 * Implement hook_field_is_empty().
1891 */
1892 function taxonomy_field_is_empty($item, $field) {
1893 if (empty($item['value']) && (string) $item['value'] !== '0') {
1894 return TRUE;
1895 }
1896 return FALSE;
1897 }
1898
1899 /**
1900 * Implement hook_field_formatter_info().
1901 */
1902 function taxonomy_field_formatter_info() {
1903 return array(
1904 'taxonomy_term_link' => array(
1905 'label' => t('Link'),
1906 'field types' => array('taxonomy_term'),
1907 'behaviors' => array(
1908 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1909 ),
1910 ),
1911 'taxonomy_term_plain' => array(
1912 'label' => t('Plain text'),
1913 'field types' => array('taxonomy_term'),
1914 'behaviors' => array(
1915 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1916 ),
1917 ),
1918 );
1919 }
1920
1921 /**
1922 * Theme function for 'link' term field formatter.
1923 */
1924 function theme_field_formatter_taxonomy_term_link($element) {
1925 $term = $element['#item']['taxonomy_term'];
1926 return l($term->name, taxonomy_term_path($term));
1927 }
1928
1929 /**
1930 * Theme function for 'plain' term field formatter.
1931 */
1932 function theme_field_formatter_taxonomy_term_plain($element) {
1933 $term = $element['#item']['taxonomy_term'];
1934 return $term->name;
1935 }
1936
1937 /**
1938 * Create an array of the allowed values for this field.
1939 *
1940 * Call the field's allowed_values function to retrieve the allowed
1941 * values array.
1942 *
1943 * @see _taxonomy_term_select()
1944 */
1945 function taxonomy_allowed_values($field) {
1946 $options = array();
1947 foreach ($field['settings']['allowed_values'] as $tree) {
1948 $terms = taxonomy_get_tree($tree['vid'], $tree['parent']);
1949 if ($terms) {
1950 foreach ($terms as $term) {
1951 $options[$term->tid] = str_repeat('-', $term->depth) . $term->name;
1952 }
1953 }
1954 }
1955 return $options;
1956 }
1957
1958 /**
1959 * Implement hook_field_load().
1960 *
1961 * This preloads all taxonomy terms for multiple loaded objects at once and
1962 * unsets values for invalid terms that do not exist.
1963 */
1964 function taxonomy_field_load($obj_type, $objects, $field, $instances, &$items, $age) {
1965 $tids = array();
1966
1967 // Collect every possible term attached to any of the fieldable entities.
1968 foreach ($objects as $id => $object) {
1969 foreach ($items[$id] as $delta => $item) {
1970 // Force the array key to prevent duplicates.
1971 $tids[$item['value']] = $item['value'];
1972 }
1973 }
1974 if ($tids) {
1975 $terms = array();
1976
1977 // Avoid calling taxonomy_term_load_multiple because it could lead to
1978 // circular references.
1979 $query = db_select('taxonomy_term_data', 't');
1980 $query->fields('t');
1981 $query->condition('t.tid', $tids, 'IN');
1982 $query->addTag('term_access');
1983 $terms = $query->execute()->fetchAllAssoc('tid');
1984
1985 // Iterate through the fieldable entities again to attach the loaded term data.
1986 foreach ($objects as $id => $object) {
1987 foreach ($items[$id] as $delta => $item) {
1988 // Check whether the taxonomy term field instance value could be loaded.
1989 if (isset($terms[$item['value']])) {
1990 // Replace the instance value with the term data.
1991 $items[$id][$delta]['taxonomy_term'] = $terms[$item['value']];
1992 }
1993 // Otherwise, unset the instance value, since the term does not exist.
1994 else {
1995 unset($items[$id][$delta]);
1996 }
1997 }
1998 }
1999 }
2000 }
2001
2002 /**
2003 * Helper function that clears field cache when terms are updated or deleted
2004 */
2005 function _taxonomy_clean_field_cache($term) {
2006 $cids = array();
2007
2008 // Determine object types that are not cacheable.
2009 $obj_types = array();
2010 foreach (field_info_fieldable_types() as $obj_type => $info) {
2011 if (!$info['cacheable']) {
2012 $obj_types[] = $obj_type;
2013 }
2014 }
2015
2016 // Load info for all taxonomy term fields.
2017 $fields = field_read_fields(array('type' => 'taxonomy_term'));
2018 foreach ($fields as $field_name => $field) {
2019
2020 // Assemble an array of vocabulary IDs that are used in this field.
2021 foreach ($field['settings']['allowed_values'] as $tree) {
2022 $vids[$tree['vid']] = $tree['vid'];
2023 }
2024
2025 // Check this term's vocabulary against those used for the field's options.
2026 if (in_array($term->vid, $vids)) {
2027 $conditions = array(array('value', $term->tid));
2028 if ($obj_types) {
2029 $conditions[] = array('type', $obj_types, 'NOT IN');
2030 }
2031 $results = field_attach_query($field['id'], $conditions, FIELD_QUERY_NO_LIMIT);
2032 foreach ($results as $obj_type => $objects) {
2033 foreach (array_keys($objects) as $id) {
2034 $cids[] = "field:$obj_type:$id";
2035 }
2036 }
2037 }
2038 }
2039 if ($cids) {
2040 cache_clear_all($cids, 'cache_field');
2041 }
2042 }
2043
2044 /**
2045 * Title callback for term pages.
2046 *
2047 * @param $term
2048 * A term object.
2049 * @return
2050 * The term name to be used as the page title.
2051 */
2052 function taxonomy_term_title($term) {
2053 return check_plain($term->name);
2054 }
2055

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.