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 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 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 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
      if (count(cdm_get_taxontrees_as_options()) > 1) {
79
        $block['content'] = cdm_taxonomictree_selector();
80
      }
81
      $block['content'] .= theme('cdm_taxontree_block', array(
82
        'tree' => $tree,
83
        'delta' => $delta,
84
        'magicbox' => $magicbox_enable == 1,
85
        'show_filter_switch' => FALSE,
86
        // 'cdm_taxontree_node_concept_switch'
87
       ));
88
      cdm_taxontree_add_js('#block-cdm-taxontree-cdm-tree', $magicbox_enable);
89
      return $block;
90

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

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

    
109
  }
110
}
111

    
112
/**
113
 * Implements hook_block_configure().
114
 */
115
function cdm_taxontree_block_configure($delta) {
116
  // TODO Rename block deltas (e.g. '1') to readable strings.
117
  if (TRUE) {
118
    switch ($delta) {
119
      case '1':
120
        $vocs = taxonomy_get_vocabularies();
121
        $options = array();
122
        foreach ($vocs as $voc) {
123
          $options[$voc->vid] = $voc->name;
124
        }
125
        $form['vid'] = array(
126
          '#type' => 'select',
127
          '#title' => t('Category'),
128
          '#default_value' => variable_get('cdm_taxontree_block_1_vid', 0),
129
          '#options' => $options,
130
        );
131
        return $form;
132
    }
133
  }
134
}
135

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

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

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

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

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

    
199
        case 'Georegion':
200
          $link = 'interest_by_georegion/';
201
          break;
202

    
203
        default:
204
          $link = 'taxonomy/term/';
205
      }
206
      return l($term->name, $link . $term->tid);
207

    
208
    default:
209
      $name = db_query('SELECT name FROM {taxonomy_term_data} WHERE tid = :tid', array(':tid' => $item['tid']))->fetchField();
210
      return $name;
211
  }
212
}
213

    
214

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

    
223
  if (isset($tids) && is_array($tids)) {
224

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

    
241
  if ($include_children) {
242
    foreach ($tid_list as $tid) {
243
      _cdm_taxontree_get_children($tid_list, $tid);
244
    }
245
  }
246

    
247
  return $tid_list;
248
}
249

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

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

    
276
  if ($_GET['destination']) {
277
    $destination = $_GET['destination'];
278
    unset($_GET['destination']);
279
    drupal_goto($destination);
280
  }
281
}
282

    
283
/**
284
 * Enter description here...
285
 *
286
 * @param string $secUuid
287
 *
288
 * @return unknown
289
 */
290
function cdm_taxontree_secRefTitle_for($secUuid) {
291

    
292
  $reference = cdm_api_secref_cache_get($secUuid);
293
  if ($reference) {
294
    $cit = $reference->titleCache;
295
  }
296
  else {
297
    $cit = '[no title for:' . $secUuid . ']';
298
  }
299
  return $cit;
300
}
301

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

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

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

    
376
/**
377
 * Enter description here...
378
 *
379
 * @param unknown_type $tid
380
 * @param unknown_type $vid
381
 * @param unknown_type $theme
382
 */
383
function cdm_taxontree_taxonomy_children($tid, $vid, $theme) {
384
  $args = func_get_args();
385
  $tid = array_shift($args);
386
  $vid = array_shift($args);
387
  $theme = array_shift($args);
388

    
389
  $children = cdm_taxontree_get_children($tid, $vid);
390
  $children = cdm_taxontree_terms2treenodes($children);
391
  array_unshift($args, $theme, $children);
392
  print call_user_func_array('theme', $args);
393
}
394

    
395
/**
396
 * Get the root level of the tree
397
 */
398
function cdm_taxontree_get_root($vid = NULL) {
399
  if (is_numeric($vid)) {
400
    // vid, $parent = 0, $depth = -1, $max_depth = NULL) {
401
    $terms = taxonomy_get_tree($vid, 0, 1);
402
    return cdm_taxontree_terms2treenodes($terms);
403
  }
404
  else {
405
    return cdm_ws_taxonomy_root_level();
406
  }
407
}
408

    
409
/**
410
 * @todo Enter description here...
411
 *
412
 * @param unknown_type $uuid
413
 * @param unknown_type $vid
414
 *
415
 * @return unknown
416
 */
417
function cdm_taxontree_get_children($uuid, $vid = NULL) {
418

    
419
  if (is_numeric($vid)) {
420
    $terms = taxonomy_get_children($uuid, $vid);
421
    return cdm_taxontree_terms2treenodes($terms);
422
  }
423
  else {
424
    // FIXME Replace $uuid by path of parent $uuids.
425
    return cdm_ws_taxonomy_root_level($uuid);
426
  }
427
}
428

    
429
/**
430
 * @todo Enter description here...
431
 *
432
 * @param unknown_type $uuid
433
 *
434
 * @return unknown
435
 */
436
function cdm_taxontree_get_parents($uuid) {
437

    
438
  if (!is_uuid($uuid)) {
439
    // Using Drupal taxonomy.
440
    $terms = taxonomy_get_parents($uuid);
441
    array_push($terms, taxonomy_term_load($uuid));
442
    $terms = array_reverse($terms);
443
    return cdm_taxontree_terms2treenodes($terms);
444
  }
445
  else {
446
    // Using cdm.
447
    $terms = cdm_ws_taxonomy_pathFromRoot($uuid);
448
    if (!$terms) {
449
      return;
450
    }
451
    $terms = array_reverse($terms);
452
    return $terms;
453
  }
454
}
455

    
456
/**
457
 * Builds a tree of TaxonNode instances
458
 *
459
 * When building the tree, the instances are extended by some fields:
460
 *  - $node->filter: values ( 'on', 'excluded', 'included' )
461
 *  - $node->expanded: values ( 'expanded', 'collapsed' )
462
 *    $node->focused: values ( TRUE, FALSE )
463
 *
464
 * @param string $taxonUuid
465
 *
466
 * @return unknown
467
 */
468
function cdm_taxontree_build_tree($taxonUuid = NULL, $hideOtherConcepts = TRUE, $vid = NULL) {
469
  // TODO Remove $hideOtherConcepts from method signature.
470
  if (is_null($vid)) {
471
    if ($taxonUuid) {
472
      $taxon = cdm_ws_get(CDM_WS_PORTAL_TAXON, $taxonUuid);
473
    }
474

    
475
    $compact_tree = cdm_taxontree_filters_active() && _get_compact_mode() != 'expanded';
476
  }
477
  // Valid compact_modes: 'expanded', 'compact', 'flattened'.
478
  // Get the root level.
479
  $root_tree = cdm_taxontree_get_root($vid);
480
  /*
481
  if(!$root_tree || !is_array($root_tree)){
482
    return array();
483
  }
484
  */
485
  $root_tree = _cdm_resultset2nodelist($root_tree, cdm_taxontree_filters_active());
486

    
487
  if (cdm_taxontree_filters_active()) {
488
    // The paths up to active filters are inactive in the user interface and
489
    // thus cannot be browsed by expanding nodes.
490
    // Therefore we need to build up the branches for all nodes which are set
491
    // as filters. The branches are merged with the root.
492
    foreach (cdm_taxontree_filters_get() as $uuid => $filter) {
493
      $branch = cdm_taxontree_build_path($uuid, TRUE, ($compact_tree === FALSE ? TRUE : NULL));
494
      $root_tree = _cdm_taxontree_merge($root_tree, $branch);
495
    }
496
  }
497

    
498
  // Build the the branch for the focused node and merge it with the root.
499
  if ($taxonUuid) {
500
    $taxon_in_current_tree = taxon_in_current_tree($taxonUuid);
501
    if ($taxon_in_current_tree) {
502
      $branch = cdm_taxontree_build_path($taxonUuid, NULL, (cdm_taxontree_filters_active() ? NULL : TRUE), TRUE);
503
      $root_tree = _cdm_taxontree_merge($root_tree, $branch);
504
    }
505
  }
506

    
507
   // Reorder siblings & populate expanded nodes with children and propagate
508
   // the filter attribute.
509
   $root_tree = cdm_taxontree_populate($root_tree, $compact_tree === FALSE);
510

    
511
  // Flatten tree.
512
  if ($compact_tree) {
513
    if (_get_compact_mode() == 'flattened') {
514
      $root_tree = cdm_taxontree_flatten($root_tree);
515
    }
516
    elseif (_get_compact_mode() == 'compact') {
517
      foreach ($root_tree as $uuid => $node) {
518
        if ($node->filter == 'excluded' && !$node->children) {
519
          unset($root_tree[$uuid]);
520
        }
521
      }
522
    }
523
  }
524

    
525
  return $root_tree;
526
}
527

    
528
/**
529
 * Builds the specific branch path for $taxonUuid.
530
 *
531
 * The branch path reaches from the parent root node of
532
 * $taxonUuid up to $taxonUuid.
533
 *
534
 * @param string $taxonUuid
535
 *   The UUID of the taxon.
536
 * @param bool $is_filter_path
537
 *   Whether the upmost node of this path is mapped by an active filter.
538
 * @param bool $is_expanded
539
 *   Whether all nodes along the tree are expanded.
540
 * @param bool $is_focused
541
 *   Whether to upper most element of this branch is set as filter.
542
 *
543
 * @return mixed
544
 *   A subtree.
545
 */
546
function cdm_taxontree_build_path($taxonUuid, $is_filter_path = NULL, $is_expanded = NULL, $is_focused = FALSE) {
547

    
548
  $branch_path = array();
549

    
550
  $parents = cdm_taxontree_get_parents($taxonUuid);
551
  if (!$parents) {
552
    if ($is_filter_path) {
553
      // \remove invalid filter.
554
      cdm_taxontree_filters_remove($taxonUuid);
555
    }
556
    return FALSE;
557
  }
558

    
559
  $parents = _cdm_resultset2nodelist($parents, NULL);
560
  $lastParent = NULL;
561

    
562
  foreach ($parents as $pnode) {
563
    $pnode->focused = FALSE;
564

    
565
    // TODO to be replaced by ($pnode->taxonUuid == $taxonUuid); ??
566
    // compare usage of $is_focused.
567
    if ($lastParent) {
568
      $pnode->children = array($lastParent->taxonUuid => $lastParent);
569
      if (!is_null($is_filter_path)) {
570
        $pnode->filter = ($is_filter_path ? 'excludes' : 'included');
571
      }
572
      if (!is_null($is_expanded)) {
573
        $pnode->expanded = ($is_expanded ? 'expanded' : 'collapsed');
574
      }
575
    }
576
    else {
577
      // The uppermost node of branch.
578
      if (!is_null($is_filter_path)) {
579
        $pnode->filter = ($is_filter_path ? 'on' : 'includes');
580
      }
581
      // Uppermost node is always expanded if it has children.
582
      $pnode->focused = $is_focused;
583
      $pnode->expanded = ($pnode->taxonomicChildrenCount ? 'expanded' : 'collapsed');
584
    }
585
    $lastParent = $pnode;
586
  }
587
  $branch_path[$pnode->taxonUuid] = $pnode;
588
  return $branch_path;
589
}
590

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

    
607
  if (!is_array($tree)) {
608
    return FALSE;
609
  }
610
  foreach (array_keys($tree) as $uuid) {
611

    
612
    if (!isset($tree[$uuid]->filter)) {
613
      $tree[$uuid]->filter = $filter_default;
614
    }
615

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

    
618
      if (isset($tree[$uuid]->vid)) {
619
        $children = cdm_taxontree_get_children($uuid, $tree[$uuid]->vid);
620
      }
621
      else {
622
        $children = cdm_taxontree_get_children($uuid);
623
      }
624
      $children = _cdm_resultset2nodelist($children, ($tree[$uuid]->filter == 'excludes'));
625

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

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

    
656
        // Sort.
657
        ksort($reordered);
658

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

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

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

    
715
  if (!$branch || !is_array($branch)) {
716
    return $tree;
717
  }
718

    
719
  if (!is_array($tree)) {
720
    return;
721
  }
722

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

    
765

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

    
786
  if (!is_array($resultset)) {
787
    return FALSE;
788
  }
789

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

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

    
817
  $path_cdm_taxontree = drupal_get_path('module', 'cdm_taxontree');
818
  $path_preferred_module = drupal_get_path('module', 'cdm_dataportal') ? drupal_get_path('module', 'cdm_dataportal') : $path_cdm_taxontree;
819
  drupal_add_css($path_cdm_taxontree . '/cdm_taxontree.css');
820
  drupal_add_js($path_preferred_module . '/js/jquery.dimensions.pack.js');
821
  drupal_add_js($path_cdm_taxontree . '/js/cdm_taxontree.js');
822
  drupal_add_js($path_cdm_taxontree . '/js/jquery.scrollTo-1.4.3.1-min.js');
823
  drupal_add_js('
824
      jQuery(document).ready(function()
825
      {
826
        jQuery(\'' . $parent_element_selector . '\').cdm_taxontree();
827
      });
828
      ', array('type' => 'inline'));
829
}
830

    
831
// ------------------------ THEME --------------------------- //
832

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

    
864
  // THEMERS: change the line below according to the
865
  // specific regions of your theme.
866
  $left_expand_region = 'right';
867

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

    
887
  $out .= theme('cdm_taxontree', array(
888
    'tree' => $tree,
889
    'filterIncludes' => !cdm_taxontree_filters_active(),
890
    'show_filter_switch' => $show_filter_switch,
891
    'tree_node_callback' => $tree_node_callback,
892
    ));
893

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

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

    
910
  static $modes = array('expanded', 'compact', 'flattened');
911

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

    
923
  return $out . '</div>';
924
}
925

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

    
937
  if (!is_array($tree)) {
938
    /*
939
    $out = '<ul class="cdm_taxontree">';
940
    $out .= '<li>----------------------------NO TREE---------------------------------------</li>';
941
    $out .= '</ul>';
942
    return $out;
943
    */
944
    return FALSE;
945
  }
946

    
947
  if (is_null($filterIncludes)) {
948
    // Set $filterIncludes TRUE if no filters are set.
949
    $filterIncludes = !cdm_taxontree_filters_active();
950
  }
951

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

    
966
/**
967
 * @todo Please document this function.
968
 * @see http://drupal.org/node/1354
969
 */
970
function theme_cdm_taxontree_node($variables) {
971
  $node = $variables['node'];
972
  $filterIncludes = $variables['filterIncludes'];
973
  $show_filter_switch = $variables['show_filter_switch'];
974
  $tree_node_callback = $variables['tree_node_callback'];
975
  $is_leaf = !$node->taxonomicChildrenCount || $node->taxonomicChildrenCount == 0;
976
  $is_expanded = isset($node->expanded) && $node->expanded = 'expanded';
977

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

    
983
    $show_filter_switch = FALSE;
984

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

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

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

    
1024
  // List item.
1025
  $out = '<li class="'
1026
    . (isset($node->focused) && $node->focused === TRUE ? 'focused ' : '')
1027
    . ($is_leaf ? 'leaf ' : ($is_expanded ? 'expanded ' : 'collapsed '))
1028
    . $filter_class . '"'
1029
    . ($ahah_url ? ' data-cdm-ahah-url="' . $ahah_url . '"' : '')
1030
    . '>';
1031

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

    
1037
  // Taxon name.
1038
  $out .= $name;
1039

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

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

    
1055
  return $out;
1056
}
1057

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

    
1069
  $out = '';
1070
  switch ($filter_class) {
1071
    case 'filter_included':
1072
      $filter_icon = 'visible_implicit_small.gif';
1073
      break;
1074

    
1075
    case 'filter_excluded':
1076
      $filter_icon = 'invisible_small.gif';
1077
      break;
1078

    
1079
    case 'filter_on':
1080
      $filter_icon = 'visible_small.gif';
1081
      break;
1082

    
1083
  }
1084

    
1085
  $filter_op = $node->filter == 'on' ? 'remove' : 'add';
1086

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

    
1097
  return $out;
1098
}
1099

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

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

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

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

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

    
1184
      case 'remove':
1185
        cdm_taxontree_filters_remove($taxonUuid);
1186
        break;
1187

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

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

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

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

    
1238
  $parents = cdm_ws_taxonomy_pathFromRoot($taxonUuid);
1239

    
1240
  $parents = array_reverse($parents);
1241

    
1242
  // Pop off last element since this is the TreeNode object for $taxonUuid!
1243
  $this_node = array_pop($parents);
1244

    
1245
  // Will contain the uuid of the parent nodes excluding the
1246
  // $taxonUuid node itself.
1247
  $parent_uuids = array();
1248

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

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

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

    
1275
// ------------------------ PRIVATE --------------------------- //
1276
/**
1277
 * Analyses the current Drupal path to get the taxon UUID.
1278
 *
1279
 * If a certain taxon was requested in the request, returns the UUID
1280
 * of that taxon. Returns a stored UUID if no taxon was requested.
1281
 * TODO where does the stored UUID come from?
1282
 *
1283
 * @return string
1284
 *   The taxon id found, or FALSE.
1285
 */
1286
function _cdm_get_taxonuuid() {
1287
  // TODO Make the path configurable.
1288
  if (arg(0) == "cdm_dataportal" && arg(1) == "taxon" && arg(2) !== 0) {
1289
    $taxon_uuid = arg(2);
1290
  }
1291
  else {
1292
    $taxon_uuid = FALSE;
1293

    
1294
    if (isset($_SESSION['cdm_dataportal']['tree']['taxon_uuid'])) {
1295
      $taxon_uuid = $_SESSION['cdm_dataportal']['tree']['taxon_uuid'];
1296
    }
1297

    
1298
  }
1299
  return $taxon_uuid;
1300
}
(5-5/16)