Project

General

Profile

Download (37.5 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/**
3
 * @file
4
 * Displays a taxon tree in a CDM Dataportal.
5
 *
6
 * @copyright
7
 *   (C) 2007-2012 EDIT
8
 *   European Distributed Institute of Taxonomy
9
 *   http://www.e-taxonomy.eu
10
 *
11
 *   The contents of this module are subject to the Mozilla
12
 *   Public License Version 1.1.
13
 * @see http://www.mozilla.org/MPL/MPL-1.1.html
14
 *
15
 * @author
16
 *   - Andreas Kohlbecker <a.kohlbecker@BGBM.org>
17
 *   - Wouter Addink <w.addink@eti.uva.nl> (migration from Drupal 5 to Drupal7)
18
 */
19

    
20
/**
21
 * Implements hook_menu() 
22
 */
23
function cdm_taxontree_menu() {
24

    
25
  $items = array();
26

    
27
  $items['cdm_taxontree/set'] = array(
28
    'page callback' => 'cdm_taxontree_set',
29
    'access arguments' => array('access cdm content'),
30
    'type' => MENU_CALLBACK,
31
  );
32

    
33
  $items['cdm_taxontree/filter'] = array(
34
    'page callback' => 'cdm_taxontree_view_filter',
35
    'access arguments' => array('access cdm content'),
36
    'type' => MENU_CALLBACK,
37
  );
38

    
39
  $items['cdm_taxontree/taxonomy/children'] = array(
40
    'page callback' => 'cdm_taxontree_taxonomy_children',
41
    'access arguments' => array('access cdm content'),
42
    'type' => MENU_CALLBACK,
43
  );
44

    
45

    
46
  return $items;
47
}
48

    
49
/**
50
 * Implements hook_block_info().
51
 */
52
function cdm_taxontree_block_info() {
53
  $block['cdm_tree'] = array(
54
        "info" => t('CDM taxon tree'),
55
        "cache" => DRUPAL_NO_CACHE
56
      );
57

    
58
  $block['cdm_tree_filters']['info'] = t('Active filters');
59
  $block['cdm_tree_drupal_taxonomy']['info'] = t('Drupal taxonomy tree');
60
  return $block;
61
}
62

    
63
/**
64
 * Implements hook_block_view().
65
 */
66
function cdm_taxontree_block_view($delta) {
67

    
68
  switch ($delta) {
69
    case 'cdm_tree':
70
      $block['subject'] = t('Classification');
71
      $taxonUuid_inFocus = _cdm_get_taxonuuid();
72
      $tree = cdm_taxontree_build_tree($taxonUuid_inFocus);
73

    
74
      // cdm_taxontree_magicbox_enable is not yet made available via the settings, so $magicbox_enable is always 0
75
      $magicbox_enable = variable_get('cdm_taxontree_magicbox_enable', 0);
76

    
77
      $block['content'] = '';
78
      $taxonomictree_selector_form = cdm_taxonomictree_selector();
79
      if (count($taxonomictree_selector_form['val']['#options']) > 1) {
80
        $block['content'] = drupal_render($taxonomictree_selector_form);
81
      }
82
      $block['content'] .= theme('cdm_taxontree_block', array(
83
        'tree' => $tree,
84
        'delta' => $delta,
85
        'magicbox' => $magicbox_enable == 1,
86
        'show_filter_switch' => FALSE,
87
        // 'cdm_taxontree_node_concept_switch'
88
       ));
89
      cdm_taxontree_add_js('#block-cdm-taxontree-cdm-tree', $magicbox_enable);
90
      return $block;
91

    
92
    case 'cdm_tree_filters':
93
      $block['subject'] = t('Active filters');
94
      $block['content'] = cdm_taxontree_view_filter('list');
95
      return $block;
96

    
97
    case 'cdm_tree_drupal_taxonomy':
98
      // cdm tree for a drupal taxonomy
99
      $block['subject'] = t('Taxonomy tree');
100
      $term_inFocus = arg(0) == 'taxonomy' && arg(1) == 'term' ? arg(2) : 0;
101
      $tree = cdm_taxontree_build_tree($term_inFocus, TRUE, variable_get('cdm_taxontree_block_1_vid', 0));
102
      $block['content'] = theme('cdm_taxontree_block', array(
103
        'tree' => $tree,
104
        'delta' => $delta,
105
        'magicbox' => FALSE,
106
      ));
107
      cdm_taxontree_add_js('#block-cdm-taxontree-cdm-tree-drupal-taxonomy');
108
      return $block;
109

    
110
  }
111
}
112

    
113
/**
114
 * Implements hook_block_configure().
115
 */
116
function cdm_taxontree_block_configure($delta) {
117

    
118
  $form = array();
119
    switch ($delta) {
120
      case 'cdm_tree':
121
        /* TODO The option to use the cdm_taxontree for drupal vocabulaties also has been removed, the below code should be
122
           removed, see #7565
123
        $vocs = taxonomy_get_vocabularies();
124
        $options = array();
125
        foreach ($vocs as $voc) {
126
          $options[$voc->vid] = $voc->name;
127
        }
128
        $form['vid'] = array(
129
          '#type' => 'select',
130
          '#title' => t('Category'),
131
          '#default_value' => variable_get('cdm_taxontree_block_1_vid', 0),
132
          '#options' => $options,
133
        );
134
        */
135
    }
136
  return $form;
137
}
138

    
139
/**
140
 * Implements hook_block_save().
141
 */
142
function cdm_taxontree_block_save($delta, $edit) {
143
  // TODO Rename block deltas (e.g. '1') to readable strings.
144
  if (TRUE) {
145
    switch ($delta) {
146
      case '1':
147
        variable_set('cdm_taxontree_block_1_vid', $edit['vid']);
148
        return;
149
    }
150
  }
151
}
152

    
153
/**
154
 * Implements hook_help().
155
 */
156
function cdm_taxontree_help($path, $arg) {
157
  switch ($path) {
158
    case 'admin/modules#description':
159
      return t('Defines a selection widget for massive taxonomy structures.');
160
  }
161
}
162

    
163
/**
164
 * Implements hook_field_info().
165
 */
166
function cdm_taxontree_field_info() {
167
  return array(
168
    'cdm_taxontree' => array('label' => 'CDM Taxontree'),
169
  );
170
}
171

    
172
/**
173
 * @todo Please document this function.
174
 * @see http://drupal.org/node/1354
175
 */
176
function cdm_taxontree_field_formatter_info() {
177
  return array(
178
    'default' => array(
179
      'label' => t('Default'),
180
      'field types' => array('cdm_taxontree'),
181
    ),
182
    'link' => array(
183
      'label' => t('With link'),
184
      'field types' => array('cdm_taxontree'),
185
    ),
186
  );
187
}
188

    
189
/**
190
 * Formatters to prepare the correct links for taxa.
191
 */
192
function cdm_taxontree_field_formatter($field, $item, $formatter, $node) {
193
  switch ($formatter) {
194
    // TODO remove case, see #7565
195
    case 'link':
196
      $term = taxonomy_term_load($item['tid']);
197
      $taxa = db_query('SELECT name FROM {taxonomy_vocabulary} WHERE vid = :vid', array(':vid' => $term->vid))->fetchField();
198
      switch ($taxa) {
199
        case 'Taxonomy':
200
          $link = 'interest_by_taxonomy/';
201
          break;
202

    
203
        case 'Georegion':
204
          $link = 'interest_by_georegion/';
205
          break;
206

    
207
        default:
208
          $link = 'taxonomy/term/';
209
      }
210
      return l($term->name, $link . $term->tid);
211

    
212
    default:
213
      $name = db_query('SELECT name FROM {taxonomy_term_data} WHERE tid = :tid', array(':tid' => $item['tid']))->fetchField();
214
      return $name;
215
  }
216
}
217

    
218

    
219
/**
220
 * Transforms an unpredictably and irregularly nested set of tids (as returned
221
 * from a taxonomy form) into a linear array of tids.
222
 * borrow from taxonomy_browser.module
223
 */
224
function _cdm_taxontree_get_all_children($tids = NULL, $include_children = FALSE) {
225
  static $tid_list = array();
226

    
227
  if (isset($tids) && is_array($tids)) {
228

    
229
    foreach ($tids as $key => $tid) {
230
      if (!empty($tid)) {
231
        if (is_array($tid)) {
232
          foreach ($tid as $key2 => $tid2) {
233
            if (!empty($tid2)) {
234
              $tid_list[$tid2] = $tid2;
235
            }
236
          }
237
        }
238
        else {
239
          $tid_list[$tid] = $tid;
240
        }
241
      } /* end !empty */
242
    } /* end foreach */
243
  }
244

    
245
  if ($include_children) {
246
    foreach ($tid_list as $tid) {
247
      _cdm_taxontree_get_children($tid_list, $tid);
248
    }
249
  }
250

    
251
  return $tid_list;
252
}
253

    
254
/**
255
 * @todo Please document this function.
256
 * @see http://drupal.org/node/1354
257
 */
258
function _cdm_taxontree_get_children(&$tid_list, $tid) {
259
  $child_nodes = taxonomy_get_children($tid);
260
  if (!empty($child_nodes)) {
261
    foreach ($child_nodes as $child_tid => $child_term) {
262
      $tid_list[$tid] = $tid;
263
      _cdm_taxontree_get_children($tid_list, $child_tid);
264
    }
265
  }
266
  else {
267
    $tid_list[$tid] = $tid;
268
  }
269
}
270

    
271
/**
272
 * @todo Please document this function.
273
 * @see http://drupal.org/node/1354
274
 */
275
function cdm_taxontree_set($key, $value) {
276
  if (is_string($key)) {
277
    $_SESSION['cdm']['taxontree'][$key] = $value;
278
  }
279

    
280
  if ($_GET['destination']) {
281
    $destination = $_GET['destination'];
282
    unset($_GET['destination']);
283
    drupal_goto($destination);
284
  }
285
}
286

    
287
/**
288
 * Enter description here...
289
 *
290
 * @param string $secUuid
291
 *
292
 * @return unknown
293
 */
294
function cdm_taxontree_secRefTitle_for($secUuid) {
295

    
296
  $reference = cdm_api_secref_cache_get($secUuid);
297
  if ($reference) {
298
    $cit = $reference->titleCache;
299
  }
300
  else {
301
    $cit = '[no title for:' . $secUuid . ']';
302
  }
303
  return $cit;
304
}
305

    
306
/**
307
 * Queries the Drupal db for location of a certain block with the given $delta.
308
 *
309
 * @param mixed $delta
310
 *   String or number identifying the block.
311
 *
312
 * @return string
313
 *   The location: left, right or <empty>.
314
 */
315
function _get_block_region($delta) {
316
  global $user, $theme_key;
317
  // Comment @WA you need to repace this with db_select if other modules
318
  // should be able to overrride this.
319
  $result = db_query("
320
    SELECT DISTINCT b.region
321
    FROM {block} b
322
    LEFT JOIN {block_role} r
323
    ON b.module = r.module
324
    AND b.delta = r.delta
325
    WHERE b.theme = :b.theme
326
    AND b.status = :b.status
327
    AND (r.rid IN (:r.rid) OR r.rid IS NULL)
328
    AND b.module = :b.module
329
    AND b.delta = :b.delta
330
  ", array(
331
    ':b.theme' => $theme_key,
332
    ':b.status' => 1,
333
    ':r.rid' => implode(',', array_keys($user->roles)),
334
    ':b.module' => 'cdm_taxontree',
335
    ':b.delta' => $delta,
336
  ))->fetch();
337
  return $result['region'];
338
}
339

    
340
/**
341
 * Enter description here...
342
 *
343
 * @return unknown
344
 */
345
function _get_compact_mode() {
346
  if (!isset($_SESSION['cdm']['taxontree']['compact_mode'])) {
347
    $_SESSION['cdm']['taxontree']['compact_mode'] = 'expanded';
348
  }
349
  return $_SESSION['cdm']['taxontree']['compact_mode'];
350
}
351

    
352
/**
353
 * Converts Drupal taxonomy terms into preliminary cdm tree nodes.
354
 *
355
 * An array of drupal taxonomy terms are converted into an
356
 * array of partially instantiated cdm tree nodes by adding the fields
357
 * relevant for tree node processing in cdm_taxontree.
358
 *
359
 * term => cdm tree node
360
 * ------------------------------------
361
 * tid -> uuid
362
 * name -> titleCache
363
 * taggedName
364
 * secUuid
365
 * isAccepted
366
 * taxonomicChildrenCount
367
 * alternativeConceptRefs
368
 *
369
 * @param array $terms
370
 *
371
 * TODO remove method, see #7565
372
 */
373
function cdm_taxontree_terms2treenodes(&$terms) {
374
  foreach ($terms as &$term) {
375
    $term->uuid = $term->tid;
376
    $term->titleCache = $term->name;
377
    $term->taxonomicChildrenCount = count(taxonomy_get_children($term->tid, $term->vid));
378
  }
379
  return $terms;
380
}
381

    
382
/**
383
 * Enter description here...
384
 *
385
 * @param unknown_type $tid
386
 * @param unknown_type $vid
387
 * @param unknown_type $theme
388
 *
389
 * * TODO remove method, see #7565
390
 */
391
function cdm_taxontree_taxonomy_children($tid, $vid, $theme) {
392
  $args = func_get_args();
393
  $tid = array_shift($args);
394
  $vid = array_shift($args);
395
  $theme = array_shift($args);
396

    
397
  $children = cdm_taxontree_get_children($tid, $vid);
398
  $children = cdm_taxontree_terms2treenodes($children);
399
  array_unshift($args, $theme, $children);
400
  print call_user_func_array('theme', $args);
401
}
402

    
403
/**
404
 * Get the root level of the tree
405
 */
406
function cdm_taxontree_get_root($vid = NULL) {
407
  if (is_numeric($vid)) {
408
    //  TODO remove case, see #7565
409
    // vid, $parent = 0, $depth = -1, $max_depth = NULL) {
410
    $terms = taxonomy_get_tree($vid, 0, 1);
411
    return cdm_taxontree_terms2treenodes($terms);
412
  }
413
  else {
414
    return cdm_ws_taxonomy_root_level();
415
  }
416
}
417

    
418
/**
419
 * @todo Enter description here...
420
 *
421
 * @param unknown_type $uuid
422
 * @param unknown_type $vid
423
 *
424
 * @return unknown
425
 */
426
function cdm_taxontree_get_children($uuid, $vid = NULL) {
427

    
428
  if (is_numeric($vid)) {
429
    //  TODO remove case, see #7565
430
    $terms = taxonomy_get_children($uuid, $vid);
431
    return cdm_taxontree_terms2treenodes($terms);
432
  }
433
  else {
434
    // FIXME Replace $uuid by path of parent $uuids.
435
    return cdm_ws_taxonomy_root_level($uuid);
436
  }
437
}
438

    
439
/**
440
 * @todo Enter description here...
441
 *
442
 * @param unknown_type $uuid
443
 *
444
 * @return unknown
445
 */
446
function cdm_taxontree_get_parents($uuid) {
447

    
448
  if (!is_uuid($uuid)) {
449
    // Using Drupal taxonomy.
450
    //  TODO remove case, see #7565
451
    $terms = taxonomy_get_parents($uuid);
452
    array_push($terms, taxonomy_term_load($uuid));
453
    $terms = array_reverse($terms);
454
    return cdm_taxontree_terms2treenodes($terms);
455
  }
456
  else {
457
    // Using cdm.
458
    $terms = cdm_ws_taxonomy_pathFromRoot($uuid);
459
    if (!$terms) {
460
      return;
461
    }
462
    $terms = array_reverse($terms);
463
    return $terms;
464
  }
465
}
466

    
467
/**
468
 * Builds a tree of TaxonNode instances
469
 *
470
 * When building the tree, the instances are extended by some fields:
471
 *  - $node->filter: values ( 'on', 'excluded', 'included' )
472
 *  - $node->expanded: values ( 'expanded', 'collapsed' )
473
 *
474
 * @param string $taxonUuid
475
 *
476
 * @return unknown
477
 */
478
function cdm_taxontree_build_tree($taxonUuid = NULL, $hideOtherConcepts = TRUE, $vid = NULL) {
479
  // TODO Remove $hideOtherConcepts from method signature.
480
  if (is_null($vid)) {
481
    //  TODO remove case, see #7565
482
    if ($taxonUuid) {
483
      $taxon = cdm_ws_get(CDM_WS_PORTAL_TAXON, $taxonUuid);
484
    }
485

    
486
    $compact_tree = cdm_taxontree_filters_active() && _get_compact_mode() != 'expanded';
487
  }
488
  // Valid compact_modes: 'expanded', 'compact', 'flattened'.
489
  // Get the root level.
490

    
491
  $root_tree = cdm_taxontree_get_root($vid); //  TODO remove $vid, see #7565 ?
492
  /*
493
  if(!$root_tree || !is_array($root_tree)){
494
    return array();
495
  }
496
  */
497
  $root_tree = _cdm_resultset2nodelist($root_tree, cdm_taxontree_filters_active());
498

    
499
  if (cdm_taxontree_filters_active()) {
500
    // The paths up to active filters are inactive in the user interface and
501
    // thus cannot be browsed by expanding nodes.
502
    // Therefore we need to build up the branches for all nodes which are set
503
    // as filters. The branches are merged with the root.
504
    foreach (cdm_taxontree_filters_get() as $uuid => $filter) {
505
      $branch = cdm_taxontree_build_path($uuid, TRUE, ($compact_tree === FALSE ? TRUE : NULL));
506
      $root_tree = _cdm_taxontree_merge($root_tree, $branch);
507
    }
508
  }
509

    
510
  // Build the the branch for the focused node and merge it with the root.
511
  if ($taxonUuid) {
512
    $taxon_in_current_tree = taxon_in_current_classification($taxonUuid);
513
    if ($taxon_in_current_tree) {
514
      $branch = cdm_taxontree_build_path($taxonUuid, NULL, (cdm_taxontree_filters_active() ? NULL : TRUE));
515
      $root_tree = _cdm_taxontree_merge($root_tree, $branch);
516
    }
517
  }
518

    
519
   // Reorder siblings & populate expanded nodes with children and propagate
520
   // the filter attribute.
521
   $root_tree = cdm_taxontree_populate($root_tree, $compact_tree === FALSE);
522

    
523
  // Flatten tree.
524
  if ($compact_tree) {
525
    if (_get_compact_mode() == 'flattened') {
526
      $root_tree = cdm_taxontree_flatten($root_tree);
527
    }
528
    elseif (_get_compact_mode() == 'compact') {
529
      foreach ($root_tree as $uuid => $node) {
530
        if ($node->filter == 'excluded' && !$node->children) {
531
          unset($root_tree[$uuid]);
532
        }
533
      }
534
    }
535
  }
536

    
537
  return $root_tree;
538
}
539

    
540
/**
541
 * Builds the specific branch path for $taxonUuid.
542
 *
543
 * The branch path reaches from the parent root node of
544
 * $taxonUuid up to $taxonUuid.
545
 *
546
 * @param string $taxonUuid
547
 *   The UUID of the taxon.
548
 * @param bool $is_filter_path
549
 *   Whether the upmost node of this path is mapped by an active filter.
550
 * @param bool $is_expanded
551
 *   Whether all nodes along the tree are expanded.
552
 *
553
 * @return mixed
554
 *   A subtree.
555
 */
556
function cdm_taxontree_build_path($taxonUuid, $is_filter_path = NULL, $is_expanded = NULL) {
557

    
558
  $branch_path = array();
559

    
560
  $parents = cdm_taxontree_get_parents($taxonUuid);
561
  if (!$parents) {
562
    if ($is_filter_path) {
563
      // \remove invalid filter.
564
      cdm_taxontree_filters_remove($taxonUuid);
565
    }
566
    return FALSE;
567
  }
568

    
569
  $parents = _cdm_resultset2nodelist($parents, NULL);
570
  $lastParent = NULL;
571

    
572
  foreach ($parents as $pnode) {
573
    if ($lastParent) {
574
      $pnode->children = array($lastParent->taxonUuid => $lastParent);
575
      if (!is_null($is_filter_path)) {
576
        $pnode->filter = ($is_filter_path ? 'excludes' : 'included');
577
      }
578
      if (!is_null($is_expanded)) {
579
        $pnode->expanded = ($is_expanded ? 'expanded' : 'collapsed');
580
      }
581
    }
582
    else {
583
      // The uppermost node of branch.
584
      if (!is_null($is_filter_path)) {
585
        $pnode->filter = ($is_filter_path ? 'on' : 'includes');
586
      }
587
      $pnode->expanded = ($pnode->taxonomicChildrenCount ? 'expanded' : 'collapsed');
588
    }
589
    $lastParent = $pnode;
590
  }
591
  $branch_path[$pnode->taxonUuid] = $pnode;
592
  return $branch_path;
593
}
594

    
595
/**
596
 * Order a tree and populate expanded nodes.
597
 *
598
 * Performs two steps on each level of the tree:
599
 *  1. Reorder siblings except root (which is expected to be ordered already)
600
 *     alphabetically.
601
 *  2. Populate children of expanded nodes  & propagate the filter attribute
602
 *
603
 * @param array $tree
604
 * @param unknown $expand_excluded
605
 * @param unknown $filter_default
606
 *
607
 * @return unknown
608
 */
609
function cdm_taxontree_populate($tree, $expand_excluded, $filter_default = NULL) {
610

    
611
  if (!is_array($tree)) {
612
    return FALSE;
613
  }
614
  foreach (array_keys($tree) as $uuid) {
615

    
616
    if (!isset($tree[$uuid]->filter)) {
617
      $tree[$uuid]->filter = $filter_default;
618
    }
619

    
620
    if (isset($tree[$uuid]->expanded) && $tree[$uuid]->expanded == 'expanded' && ($expand_excluded || $tree[$uuid]->filter != 'excluded')) {
621

    
622
      if (isset($tree[$uuid]->vid)) {
623
        //  TODO remove case, see #7565
624
        $children = cdm_taxontree_get_children($uuid, $tree[$uuid]->vid);
625
      }
626
      else {
627
        $children = cdm_taxontree_get_children($uuid);
628
      }
629
      $children = _cdm_resultset2nodelist($children, ($tree[$uuid]->filter == 'excludes'));
630

    
631
      // Store the children of the node for later processing.
632
      if (isset($tree[$uuid]->children) && is_array($tree[$uuid]->children)) {
633
        $pnode_children = $tree[$uuid]->children;
634
      }
635
      else {
636
        $pnode_children = FALSE;
637
      }
638
      // Replace the children by the newly retrieved child nodes.
639
      $tree[$uuid]->children = $children;
640

    
641
      if ($pnode_children) {
642
        // Recurse into the childtree which was stored before.
643
        $pnode_children = cdm_taxontree_populate($pnode_children, $expand_excluded, $tree[$uuid]->filter);
644
        // Recombine.
645
        foreach ($pnode_children as $childUuid => $cnode) {
646
          $tree[$uuid]->children[$childUuid] = $cnode;
647
        }
648
      }
649
    }
650
    else {
651
      // Reorder nodes which are not expanded, expanded nodes are reordered
652
      // implicitly above.
653
      if (isset($tree[$uuid]->children) && count($tree[$uuid]->children) > 1) {
654
        // Copy the children into an array which can be sorted by its keys.
655
        $ordered = array();
656
        foreach ($tree[$uuid]->children as $cnode) {
657
          // Concatenate full name and uid.
658
          $reordered[str_pad($cnode->titleCache, 255, '-') . $cnode->taxonUuid] = $cnode;
659
        }
660

    
661
        // Sort.
662
        ksort($reordered);
663

    
664
        // Move the children back into the parent node.
665
        $tree[$uuid]->children = array();
666
        foreach ($reordered as $cnode) {
667
          $tree[$uuid]->children[$cnode->taxonUuid] = $cnode;
668
        }
669
      }
670
      if (!isset($tree[$uuid]->children)) {
671
        $tree[$uuid]->children = FALSE;
672
      }
673
      $tree[$uuid]->children = cdm_taxontree_populate($tree[$uuid]->children, $expand_excluded, $tree[$uuid]->filter);
674
    }
675
  }
676
  return $tree;
677
}
678

    
679
/**
680
 * Enter description here...
681
 *
682
 * @param array $tree
683
 *   Tree to flatten.
684
 * @param array $new_root
685
 *
686
 * @return unknown
687
 */
688
function cdm_taxontree_flatten($tree, &$new_root = NULL) {
689
  if (empty($new_root)) {
690
    $new_root = array();
691
  }
692
  foreach ($tree as $node) {
693
    if ($node->filter == 'on') {
694
      $new_root[$node->taxonUuid] = $node;
695
    }
696
    elseif (is_array($node->children)) {
697
      cdm_taxontree_flatten($node->children, $new_root);
698
    }
699
  }
700
  return $new_root;
701
}
702

    
703
/**
704
 * Merge a branch into a tree.
705
 *
706
 * Merge a branch into a tree whereas the tree dominated the branch except
707
 * nodes having property filter set to "on". These always dominate
708
 * nevertheless if they are in tree or branch.
709
 *
710
 * @param array $tree
711
 *   The dominant tree.
712
 * @param array $branch
713
 *   The tree to be merged in.
714
 *
715
 * @return array
716
 *   The merged $tree.
717
 */
718
function _cdm_taxontree_merge($tree, $branch) {
719

    
720
  if (!$branch || !is_array($branch)) {
721
    return $tree;
722
  }
723

    
724
  if (!is_array($tree)) {
725
    return;
726
  }
727

    
728
  foreach (array_keys($tree) as $uuid) {
729
    // Check if node exists in $branch.
730
    if (!empty($branch[$uuid])) {
731
      // Preserve filter property.
732
      if (isset($tree[$uuid]->filter) && !(isset($branch[$uuid]->filter) && $branch[$uuid]->filter == 'on')) {
733
        $branch[$uuid]->filter = $tree[$uuid]->filter;
734
      }
735
      elseif (isset($branch[$uuid]->filter)) {
736
        $tree[$uuid]->filter = $branch[$uuid]->filter;
737
      }
738
      // Preserve expanded property.
739
      if (isset($tree[$uuid]->expanded)) {
740
        $branch[$uuid]->expanded = $tree[$uuid]->expanded;
741
      }
742
      elseif (isset($branch[$uuid]->expanded)) {
743
        $tree[$uuid]->expanded = $branch[$uuid]->expanded;
744
      }
745
      // $Uuid exists check if the node in tree1 or tree2 contains children.
746
      if (isset($branch[$uuid]->children) && is_array($branch[$uuid]->children) && isset($tree[$uuid]->children) && is_array($tree[$uuid]->children)) {
747
        // Merge recursive.
748
        $tree[$uuid]->children = _cdm_taxontree_merge($tree[$uuid]->children, $branch[$uuid]->children);
749
      }
750
      elseif (isset($branch[$uuid]->children) && is_array($branch[$uuid]->children)) {
751
        $tree[$uuid] = $branch[$uuid];
752
      }
753
      unset($branch[$uuid]);
754
    }
755
  }
756
  // Append remaining items from branch to tree.
757
  foreach (array_keys($branch) as $uuid) {
758
    $tree[$uuid] = $branch[$uuid];
759
  }
760
  return $tree;
761
}
762

    
763

    
764
/**
765
 * Alter a resultset into an array of TreeNode instances with taxonUuid as keys.
766
 *
767
 * Replaces the keys of an array of TreeNode instances
768
 * by the $treenode->taxonUuid of the single array elements and sets
769
 * additional fields.
770
 *
771
 * @param array $resultset
772
 *   Array of TreeNode instances as +returned by the cdm web service.
773
 * @param mixed $excluded
774
 *   Whether the $resultset is included by a active filter. Is ignored if NULL.
775
 * @param mixed $expanded
776
 *   Whether the children of the nodes in the $resultset are expanded or not.
777
 *   Is ignored if NULL.
778
 *
779
 * @return array
780
 *   A tree of TreeNode instances with altered keys.
781
 */
782
function _cdm_resultset2nodelist($resultset, $excluded = NULL, $expanded = NULL) {
783

    
784
  if (!is_array($resultset)) {
785
    return FALSE;
786
  }
787

    
788
  $tree = array();
789
  foreach ($resultset as $treeNode) {
790
    if (!is_null($excluded)) {
791
      $treeNode->filter = ($excluded ? 'excluded' : 'included');
792
    }
793
    if (!is_null($expanded)) {
794
      $treeNode->expanded = ($expanded ? 'expanded' : 'collapsed');
795
    }
796
    $tree[$treeNode->taxonUuid] = $treeNode;
797
  }
798
  return $tree;
799
}
800

    
801
/**
802
 * Adds all java script files and also initializes the cdm_taxontree java script behaviours
803
 *
804
 * @param string $parent_element_selector
805
 *    jQuery selector of a parent element of the cdm_taxontree, This is usually a drupal block id
806
 *    selector: #block-cdm-taxontree-cdm-tree or #block-cdm-taxontree-cdm-tree-drupal-taxonomy
807
 *
808
 * @param bool $magicbox_enable
809
 *    If set to TRUE the cdm_taxontree will get the magic box behavoir, which means that it
810
 *    will extend on mouse over events in order to
811
 *    show the entrys without truncation through cropping
812
 */
813
function cdm_taxontree_add_js($parent_element_selector, $magicbox_enable = FALSE) {
814

    
815
  $path_cdm_taxontree = drupal_get_path('module', 'cdm_taxontree');
816
  $path_preferred_module = drupal_get_path('module', 'cdm_dataportal') ? drupal_get_path('module', 'cdm_dataportal') : $path_cdm_taxontree;
817
  drupal_add_css($path_cdm_taxontree . '/cdm_taxontree.css');
818
  drupal_add_js($path_cdm_taxontree . '/js/cdm_taxontree.js');
819

    
820
  drupal_add_js('
821
      jQuery(document).ready(function()
822
      {
823
        jQuery(\'' . $parent_element_selector . '\').cdm_taxontree();
824
      });
825
      ', array('type' => 'inline'));
826
}
827

    
828
// ------------------------ THEME --------------------------- //
829

    
830
/**
831
 *  Returns HTML for a Taxontree block.
832
 *
833
 * @param array $variables
834
 *   An associative array containing:
835
 *   - tree: The tree of TreeNode to be displayed.
836
 *   - magicbox: Boolean. If TRUE, the tree will be embedded into a set of
837
 *       div tags which allow the tree to expand and overlap other content.
838
 *       This is useful if the node titles are quite long or if the tree is
839
 *       nested deeply. If $magicbox ist set to the delta of the containing
840
 *       block the direction into which the box expands is dependent on the
841
 *       region in which the block is located. See also $left_expand_region
842
 *       in this function!
843
 *   - show_filter_switch: The tree can offer buttons to add a node to a set of
844
 *       filters which can then be applied to the tree to limit the visible
845
 *       subtrees and thus to compact the tree. Three different compact modes
846
 *       are available.
847
 *   - tree_node_callback: Name of a callback method which will be called
848
 *       for each node in theme_cdm_taxontree_node(). The output of this
849
 *       callback, which takes the $node object as single arument, is appended
850
 *       to the end of the rendered node.
851
 *
852
 * @ingroup themeable
853
 */
854
function theme_cdm_taxontree_block($variables) {
855
  $tree = $variables['tree'];
856
  $delta = $variables['delta'];
857
  $magicbox = $variables['magicbox'];
858
  $show_filter_switch = $variables['show_filter_switch'];
859
  $tree_node_callback = $variables['tree_node_callback'];
860

    
861
  // THEMERS: change the line below according to the
862
  // specific regions of your theme.
863
  $left_expand_region = 'right';
864

    
865
  $out = '';
866
  if (cdm_taxontree_filters_active()) {
867
    $out .= theme('cdm_taxontree_controller', array('compact_mode' => _get_compact_mode()));
868
  }
869
  $region = null;
870
  if (!empty($magicbox)) {
871
    if (is_numeric($magicbox) || is_string($magicbox)) {
872
      $region = _get_block_region($magicbox);
873
    }
874
    // The magicbox expands to the right by default,
875
    // if the class 'expand-left' to  the cdm_taxontree_scroller_x the box will
876
    // expand to the left.
877
    $expand_direction = ($region == 'right' ? 'expand-left' : '');
878
    $out .= '<div class="cdm_taxontree_scroller_x ' . $expand_direction . '"><div class="cdm_taxontree_container"><div class="cdm_taxontree_scroller_y">';
879
  }
880
  else {
881
    $out .= '<div class="cdm_taxontree_scroller_xy">';
882
  }
883

    
884
  $out .= theme('cdm_taxontree', array(
885
    'tree' => $tree,
886
    'filterIncludes' => !cdm_taxontree_filters_active(),
887
    'show_filter_switch' => $show_filter_switch,
888
    'tree_node_callback' => $tree_node_callback,
889
    ));
890

    
891
  if ($magicbox) {
892
    $out .= '</div></div></div>';
893
  }
894
  else {
895
    $out .= '</div>';
896
  }
897
  return $out;
898
}
899

    
900
/**
901
 * @todo Please document this function.
902
 * @see http://drupal.org/node/1354
903
 */
904
function theme_cdm_taxontree_controller($variables) {
905
  $compact_mode = $variables['compact_mode'];
906

    
907
  static $modes = array('expanded', 'compact', 'flattened');
908

    
909
  $out = '<div class="settings">';
910
  foreach ($modes as $mode) {
911
    if ($compact_mode == $mode) {
912
      $out .= t('@mode', array('@mode' => $mode));
913
    }
914
    else {
915
      $out .= l(t('@mode', array('@mode' => $mode)), 'cdm_taxontree/set/compact_mode/' . $mode, array('query' => drupal_get_destination()));
916
    }
917
    $out .= ' ';
918
  }
919

    
920
  return $out . '</div>';
921
}
922

    
923
/**
924
 * @todo Please document this function.
925
 * @see http://drupal.org/node/1354
926
 */
927
function theme_cdm_taxontree($variables) {
928
  $tree = $variables['tree'];
929
  $filterIncludes = $variables['filterIncludes'];
930
  $show_filter_switch = $variables['show_filter_switch'];
931
  $tree_node_callback = $variables['tree_node_callback'];
932
  $element_name = $variables['element_name'];
933

    
934
  if(isset($tree->records)){
935
    // unwrap from pager object
936
    $tree = $tree->records;
937
  }
938

    
939
  if (!is_array($tree)) {
940
    return FALSE;
941
  }
942

    
943
  if (is_null($filterIncludes)) {
944
    // Set $filterIncludes TRUE if no filters are set.
945
    $filterIncludes = !cdm_taxontree_filters_active();
946
  }
947

    
948
  // Append element name to get multiple taxontrees on one page working.
949
  $out = '<ul class="taxon-nodes ' . (($element_name) ? ' ' . $element_name : '') . '">';
950
  foreach ($tree as $node) {
951
    if($node == null){
952
      continue;
953
    }
954
    $out .= theme('cdm_taxontree_node', array(
955
      'node' => $node,
956
      'filterIncludes' => $filterIncludes,
957
      'show_filter_switch' => $show_filter_switch,
958
      'tree_node_callback' => $tree_node_callback
959
      ));
960
  }
961
  $out .= '</ul>';
962
  return $out;
963
}
964

    
965
/**
966
 * @todo Please document this function.
967
 * @see http://drupal.org/node/1354
968
 */
969
function theme_cdm_taxontree_node($variables) {
970
  $node = $variables['node'];
971
  $filterIncludes = $variables['filterIncludes'];
972
  $show_filter_switch = $variables['show_filter_switch'];
973
  $tree_node_callback = $variables['tree_node_callback'];
974

    
975
  $is_leaf = !$node->taxonomicChildrenCount || $node->taxonomicChildrenCount == 0;
976
  $is_expanded = isset($node->expanded) && $node->expanded = 'expanded';
977

    
978
  $focused_taxon_uuid = get_current_taxon_uuid();
979

    
980
  if (isset($node->tid)) {
981
    $node_name = $node->name;
982
    $path = "taxonomy/term/" . $node->tid;
983
    // disable filterswitch
984

    
985
    $show_filter_switch = FALSE;
986

    
987
  }
988
  elseif (module_exists('cdm_dataportal')) {
989
    $node_name = cdm_dataportal_shortname_of($node);
990
    $path = path_to_taxon($node->taxonUuid);
991
  }
992
  else {
993
    $node_name = "module cdm_dataportal missing";
994
    $path = "";
995
  }
996

    
997
  if ($filterIncludes) {
998
    $name = l($node_name, $path);
999
    // No names for terms in filter widget; as discussed with A. Müller.
1000
    // $name = '';
1001
    $filter_class = 'filter_included';
1002
  }
1003
  else {
1004
    if ($node->filter == 'on') {
1005
      $name = l($node_name, $path);
1006
      $filter_class = 'filter_on';
1007
    }
1008
    else {
1009
      $name = $node_name;
1010
      $filter_class = 'filter_excluded';
1011
    }
1012
  }
1013
  $nextLevelIncluded = isset($node->filter) && $node->filter == 'on' || $filterIncludes;
1014

    
1015
  $ahah_url = FALSE;
1016
  if (!$is_leaf && !$is_expanded && $filter_class != 'filter_excluded') {
1017
    if (isset($node->tid)) {
1018
      //  TODO remove case, see #7565
1019
      $ahah_url = url('cdm_taxontree/taxonomy/children/' . $node->tid . '/' . $node->vid . '/cdm_taxontree/' . ($nextLevelIncluded ? 1 : 0) . '/' . ($show_filter_switch ? 1 : 0) . '/' . $tree_node_callback);
1020
    }
1021
    elseif (module_exists('cdm_dataportal')) {
1022
      $ws_url = cdm_compose_taxonomy_root_level_path($node->taxonUuid);
1023
      $ahah_url = url('cdm_api/proxy/' . urlencode($ws_url) . '/cdm_taxontree/' . ($nextLevelIncluded ? 1 : 0) . '/' . ($show_filter_switch ? 1 : 0) . '/' . $tree_node_callback);
1024
    }
1025
  }
1026

    
1027
  // List item.
1028
  $out = '<li class="'
1029
    . ($node->taxonUuid == $focused_taxon_uuid ? 'focused ' : '')
1030
    . ($is_leaf ? 'leaf ' : ($is_expanded ? 'expanded ' : 'collapsed '))
1031
    . $filter_class . '"'
1032
    . ($ahah_url ? ' data-cdm-ahah-url="' . $ahah_url . '"' : '')
1033
    . '>';
1034

    
1035
  if ($show_filter_switch) {
1036
    // Filter icon.
1037
    $out .= theme('cdm_taxontree_node_filter_switch', array('node' => $node, 'filter_class' => $filter_class));
1038
  }
1039

    
1040
  // Taxon name.
1041
  $out .= '<span class="' . html_class_attribute_ref($node) . '">' . $name . '</span>';
1042

    
1043
  // Concept_switch or other theme callbacks.
1044
  if ($tree_node_callback) {
1045
     $out .= theme($tree_node_callback, array('node' => $node));
1046
  }
1047

    
1048
  if (isset($node->children) && is_array($node->children)) {
1049
    $out .= theme('cdm_taxontree', array(
1050
      'tree' => $node->children,
1051
      'filterIncludes' => $nextLevelIncluded,
1052
      'show_filter_switch' => $show_filter_switch,
1053
      'tree_node_callback' => $tree_node_callback,
1054
    ));
1055
  }
1056
  $out .= '</li>';
1057

    
1058
  return $out;
1059
}
1060

    
1061
/**
1062
 * @todo Please document this function.
1063
 * @see http://drupal.org/node/1354
1064
 */
1065
function theme_cdm_taxontree_node_filter_switch($variables) {
1066
  $node = $variables['node'];
1067
  $filter_class = $variables['filter_class'];
1068
  if (!module_exists('cdm_dataportal')) {
1069
    return '';
1070
  }
1071

    
1072
  $out = '';
1073
  switch ($filter_class) {
1074
    case 'filter_included':
1075
      $filter_icon = 'visible_implicit_small.gif';
1076
      break;
1077

    
1078
    case 'filter_excluded':
1079
      $filter_icon = 'invisible_small.gif';
1080
      break;
1081

    
1082
    case 'filter_on':
1083
      $filter_icon = 'visible_small.gif';
1084
      break;
1085

    
1086
  }
1087

    
1088
  $filter_op = $node->filter == 'on' ? 'remove' : 'add';
1089

    
1090
  $out .= '&nbsp;'
1091
    . l('<img src="' . base_path() . drupal_get_path('module', 'cdm_taxontree') . '/' . $filter_icon . '" alt="[f]" />',
1092
        'cdm_taxontree/filter/' . $filter_op . '/' . $node->taxonUuid, array(
1093
          'attributes' => array('class' => 'filter_' . $filter_op),
1094
          'query' => 'destination=' . path_to_taxon($node->taxonUuid),
1095
          'fragment' => NULL,
1096
          'absolute' => TRUE,
1097
          'html' => TRUE,
1098
        ));
1099

    
1100
  return $out;
1101
}
1102

    
1103
/**
1104
 * Returns HTML for a Taxontree-node concept switch.
1105
 *
1106
 * @param array $variables
1107
 *   An associative array containing:
1108
 *   - node: The node object.
1109
 *
1110
 * @ingroup themeable
1111
 */
1112
function theme_cdm_taxontree_node_concept_switch($variables) {
1113
  $node = $variables['node'];
1114
  $out = '';
1115

    
1116
  if (isset($node->alternativeConceptRefs[0])) {
1117
    $out = l(
1118
      '<img src="' . base_path() . drupal_get_path('module', 'cdm_taxontree') . '/concept_switch.gif" alt="[-&gt;]" />',
1119
      'cdm_dataportal/taxon/alternative/' . $node->taxonUuid, array(
1120
        'attributes' => array('rel' => 'cdm_dataportal/taxon/alternative/' . $node->taxonUuid, 'class' => 'concept_switch'),
1121
        'query' => NULL,
1122
        'fragment' => NULL,
1123
        'absolute' => TRUE,
1124
        'html' => TRUE,
1125
      ));
1126
  }
1127
  return $out;
1128
}
1129

    
1130
/**
1131
 * @todo Please document this function.
1132
 * @see http://drupal.org/node/1354
1133
 */
1134
function cdm_taxontree_theme() {
1135
  return array(
1136
    'cdm_taxontree' => array('variables' => array(
1137
      'tree' => NULL,
1138
      'filterIncludes' => NULL,
1139
      'show_filter_switch' => FALSE,
1140
      'tree_node_callback' => FALSE,
1141
      'element_name' => FALSE
1142
      )),
1143
    'cdm_taxontree_block' => array('variables' => array(
1144
      'tree' => NULL,
1145
      'delta' => NULL,
1146
      'magicbox' => FALSE,
1147
      'show_filter_switch' => FALSE,
1148
      'tree_node_callback' => FALSE,
1149
      )),
1150
    'cdm_taxontree_controller' => array('variables' => array('compact_mode' => NULL)),
1151
    'cdm_taxontree_node' => array('variables' => array(
1152
      'node' => NULL,
1153
      'filterIncludes' => NULL,
1154
      'show_filter_switch' => FALSE,
1155
      'tree_node_callback' => FALSE
1156
      )),
1157
    'cdm_taxontree_node_concept_switch' => array('variables' => array('node' => NULL)),
1158
    'cdm_taxontree_node_filter_switch' => array('variables' => array('node' => NULL, 'filter_class' => NULL)),
1159
  );
1160
}
1161

    
1162
// ----------------- FILTERS -------------------------- //
1163
/**
1164
 * Filters on children, overrides already set parent filters and vice versa.
1165
 *
1166
 * @param string $op
1167
 *   [add | remove] a taxon from the filtered taxa.
1168
 *   TODO at the moment there is also a 'list' operation that displays all
1169
 *   set filters and provides the ability to delete them.
1170
 *   This option depends on function is defined in cdm_dataportal.
1171
 *   Problem is, that the dependency is the other way round.
1172
 * @param string $taxonUuid
1173
 *
1174
 * @return string
1175
 */
1176
function cdm_taxontree_view_filter($op, $taxonUuid = NULL) {
1177

    
1178
  if (!isset($_SESSION['cdm']['filters'])) {
1179
    $_SESSION['cdm']['filters'] = array();
1180
  }
1181
  if ($taxonUuid || $op == 'list') {
1182
    switch ($op) {
1183
      case 'add':
1184
        cdm_taxontree_filters_add($taxonUuid);
1185
        break;
1186

    
1187
      case 'remove':
1188
        cdm_taxontree_filters_remove($taxonUuid);
1189
        break;
1190

    
1191
      case 'list':
1192
        // TODO put in cdm_dataportal_theme to decouple both modules by this!!!
1193
        $out = '<ul>';
1194
        foreach ($_SESSION['cdm']['filters'] as $uuid => $node) {
1195
          $out .= '<li>' . cdm_dataportal_shortname_of($node) . ' ' . l(t('[x]'), 'cdm_dataportal/filter/remove/' . $uuid, array('query' => drupal_get_destination())) . '</li>';
1196
        }
1197
        $out .= '</ul>';
1198
        return $out;
1199
    }
1200
  }
1201
  if ($_GET['destination']) {
1202
    $destination = $_GET['destination'];
1203
    unset($_GET['destination']);
1204
    drupal_goto($destination);
1205
  }
1206
}
1207

    
1208
/**
1209
 * Checks if there are active filters.
1210
 *
1211
 * Filters are set in cdm_dataportal_view_filter().
1212
 * functions using filters should remove invalid filters.
1213
 *
1214
 * @return bool
1215
 *   TRUE if any filter is active.
1216
 */
1217
function cdm_taxontree_filters_active() {
1218
  return isset($_SESSION['cdm']['filters']) && count($_SESSION['cdm']['filters']) > 0;
1219
}
1220

    
1221
/**
1222
 * Filters are set in cdm_dataportal_view_filter().
1223
 *
1224
 * @return mixed
1225
 *   A reference on the filters array stored in the SESSION.
1226
 */
1227
function &cdm_taxontree_filters_get() {
1228
  if (!isset($_SESSION['cdm']['filters'])) {
1229
    $_SESSION['cdm']['filters'] = array();
1230
  }
1231
  return $_SESSION['cdm']['filters'];
1232
}
1233

    
1234
/**
1235
 * Adds a taxon to the filtered taxa array
1236
 *
1237
 * @param UUID $taxonUuid
1238
 */
1239
function cdm_taxontree_filters_add($taxonUuid) {
1240

    
1241
  $parents = cdm_ws_taxonomy_pathFromRoot($taxonUuid);
1242

    
1243
  $parents = array_reverse($parents);
1244

    
1245
  // Pop off last element since this is the TreeNode object for $taxonUuid!
1246
  $this_node = array_pop($parents);
1247

    
1248
  // Will contain the uuid of the parent nodes excluding the
1249
  // $taxonUuid node itself.
1250
  $parent_uuids = array();
1251

    
1252
  // Children override parents rule: remove all parent filters.
1253
  foreach ($parents as $pnode) {
1254
    unset($_SESSION['cdm']['filters'][$pnode->taxonUuid]);
1255
    $parent_uuids[] = $pnode->taxonUuid;
1256
  }
1257

    
1258
  // Search for potential children of this $taxonUuid.
1259
  foreach ($_SESSION['cdm']['filters'] as $uuid => $node) {
1260
    if (in_array($taxonUuid, $node->parentUuids)) {
1261
      unset($_SESSION['cdm']['filters'][$node->taxonUuid]);
1262
    }
1263
  }
1264
  // Finally add this $taxonUuid as new filter.
1265
  $this_node->parentUuids = $parent_uuids;
1266
  $_SESSION['cdm']['filters'][$taxonUuid] = $this_node;
1267
}
1268

    
1269
/**
1270
 * Unsets a taxon from the filtered taxa array.
1271
 *
1272
 * @param string $taxonUuid
1273
 */
1274
function cdm_taxontree_filters_remove($taxonUuid) {
1275
  unset($_SESSION['cdm']['filters'][$taxonUuid]);
1276
}
1277

    
1278
// ------------------------ PRIVATE --------------------------- //
1279
/**
1280
 * Analyses the current Drupal path to get the taxon UUID.
1281
 *
1282
 * If a certain taxon was requested in the request, returns the UUID
1283
 * of that taxon. Returns a stored UUID if no taxon was requested.
1284
 * TODO where does the stored UUID come from?
1285
 *
1286
 * @return string
1287
 *   The taxon id found, or FALSE.
1288
 *
1289
 * @deprecated use get_current_taxon_uuid() instead
1290
 */
1291
function _cdm_get_taxonuuid() {
1292

    
1293
  if (arg(0) == "cdm_dataportal" && arg(1) == "taxon" && arg(2) !== 0) {
1294
    $taxon_uuid = arg(2);
1295
  }
1296
  else {
1297
    $taxon_uuid = FALSE;
1298
    // TODO is this SESSION variable ['cdm_dataportal']['tree']['taxon_uuid'] use at all?
1299
    if (isset($_SESSION['cdm_dataportal']['tree']['taxon_uuid'])) {
1300
      $taxon_uuid = $_SESSION['cdm_dataportal']['tree']['taxon_uuid'];
1301
    }
1302

    
1303
  }
1304
  return $taxon_uuid;
1305
}
(5-5/16)