Project

General

Profile

Download (37.4 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
      $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
  // TODO Rename block deltas (e.g. '1') to readable strings.
118
  if (TRUE) {
119
    switch ($delta) {
120
      case '1':
121
        $vocs = taxonomy_get_vocabularies();
122
        $options = array();
123
        foreach ($vocs as $voc) {
124
          $options[$voc->vid] = $voc->name;
125
        }
126
        $form['vid'] = array(
127
          '#type' => 'select',
128
          '#title' => t('Category'),
129
          '#default_value' => variable_get('cdm_taxontree_block_1_vid', 0),
130
          '#options' => $options,
131
        );
132
        return $form;
133
    }
134
  }
135
}
136

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

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

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

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

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

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

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

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

    
215

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

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

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

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

    
248
  return $tid_list;
249
}
250

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
526
  return $root_tree;
527
}
528

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

    
549
  $branch_path = array();
550

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
766

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

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

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

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

    
818
  $path_cdm_taxontree = drupal_get_path('module', 'cdm_taxontree');
819
  $path_preferred_module = drupal_get_path('module', 'cdm_dataportal') ? drupal_get_path('module', 'cdm_dataportal') : $path_cdm_taxontree;
820
  drupal_add_css($path_cdm_taxontree . '/cdm_taxontree.css');
821
  drupal_add_js($path_cdm_taxontree . '/js/cdm_taxontree.js');
822

    
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(isset($tree->records)){
938
    // unwrap from pager object
939
    $tree = $tree->records;
940
  }
941

    
942
  if (!is_array($tree)) {
943
    return FALSE;
944
  }
945

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

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

    
968
/**
969
 * @todo Please document this function.
970
 * @see http://drupal.org/node/1354
971
 */
972
function theme_cdm_taxontree_node($variables) {
973
  $node = $variables['node'];
974
  $filterIncludes = $variables['filterIncludes'];
975
  $show_filter_switch = $variables['show_filter_switch'];
976
  $tree_node_callback = $variables['tree_node_callback'];
977
  $is_leaf = !$node->taxonomicChildrenCount || $node->taxonomicChildrenCount == 0;
978
  $is_expanded = isset($node->expanded) && $node->expanded = 'expanded';
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
      $ahah_url = url('cdm_taxontree/taxonomy/children/' . $node->tid . '/' . $node->vid . '/cdm_taxontree/' . ($nextLevelIncluded ? 1 : 0) . '/' . ($show_filter_switch ? 1 : 0) . '/' . $tree_node_callback);
1019
    }
1020
    elseif (module_exists('cdm_dataportal')) {
1021
      $ws_url = cdm_compose_taxonomy_root_level_path($node->taxonUuid);
1022
      $ahah_url = url('cdm_api/proxy/' . urlencode($ws_url) . '/cdm_taxontree/' . ($nextLevelIncluded ? 1 : 0) . '/' . ($show_filter_switch ? 1 : 0) . '/' . $tree_node_callback);
1023
    }
1024
  }
1025

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

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

    
1039
  // Taxon name.
1040
  $out .= $name;
1041

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

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

    
1057
  return $out;
1058
}
1059

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

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

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

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

    
1085
  }
1086

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

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

    
1099
  return $out;
1100
}
1101

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

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

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

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

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

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

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

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

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

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

    
1240
  $parents = cdm_ws_taxonomy_pathFromRoot($taxonUuid);
1241

    
1242
  $parents = array_reverse($parents);
1243

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

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

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

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

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

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

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

    
1300
  }
1301
  return $taxon_uuid;
1302
}
(5-5/16)