Project

General

Profile

Download (37.2 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']['info'] = t('CDM taxon tree');
54
  $block['cdm_tree']['cache'] = DRUPAL_NO_CACHE;
55

    
56
  $block['filters']['info'] = t('Active filters');
57
  $block[1]['info'] = t('Drupal taxonomy tree');
58
  return $block;
59
}
60

    
61
/**
62
 * Implements hook_block_view().
63
 */
64
function cdm_taxontree_block_view($delta) {
65

    
66
  // TODO Rename block deltas (e.g. '1') to readable strings.
67
  switch ($delta) {
68
    case 'cdm_tree':
69
      $block['subject'] = t('Classification');
70
      $taxonUuid_inFocus = _cdm_get_taxonuuid();
71
      $tree = cdm_taxontree_build_tree($taxonUuid_inFocus);
72
      $magicbox_enable = variable_get('cdm_taxontree_magicbox_enable', 0);
73

    
74
      $block['content'] = '';
75
      if (count(cdm_get_taxontrees_as_options()) > 1) {
76
        $block['content'] = cdm_taxonomictree_selector();
77
      }
78
      $block['content'] .= theme('cdm_taxontree_block', array(
79
        'tree' => $tree,
80
        'delta' => $delta,
81
        'magicbox' => FALSE,
82
        'show_filter_switch' => FALSE,
83
        // 'cdm_taxontree_node_concept_switch'
84
       ));
85

    
86
      // Java script.
87
      $verticalSroller = $magicbox_enable ? 'cdm_taxontree_scroller_x' : 'cdm_taxontree_scroller_xy';
88
      theme('cdm_taxontree_add_scripts');
89

    
90
      drupal_add_js('
91
      jQuery(document).ready(function()
92
      {
93
        jQuery(\'ul.cdm_taxontree\').cdm_taxontree();
94
        jQuery(\'div.' . $verticalSroller . '\').scrollTo(jQuery(\'.focused\'), 400, {over:-3});
95
      });
96
      ', array('type' => 'inline'));
97

    
98
      return $block;
99

    
100
    case 'filters':
101
      $block['subject'] = t('Active filters');
102
      $block['content'] = cdm_taxontree_view_filter('list');
103
      return $block;
104

    
105
    case '1':
106
      $block['subject'] = t('Taxonomy tree');
107
      $term_inFocus = arg(0) == 'taxonomy' && arg(1) == 'term' ? arg(2) : 0;
108
      $tree = cdm_taxontree_build_tree($term_inFocus, TRUE, variable_get('cdm_taxontree_block_1_vid', 0));
109
      $block['content'] = theme('cdm_taxontree_block', array(
110
        'tree' => $tree,
111
        'delta' => $delta,
112
        'magicbox' => FALSE,
113
      ));
114
      theme('cdm_taxontree_add_scripts');
115
      drupal_add_js('
116

    
117
      jQuery(document).ready(function()
118
      {
119
        jQuery(\'ul.cdm_taxontree\').cdm_taxontree();
120
        jQuery(\'div.cdm_taxontree_scroller_x\').scrollTo(jQuery(\'.active\'), 400, {over:-3});});
121
      ', array('type' => 'inline'));
122

    
123
      return $block;
124

    
125
  }
126
}
127

    
128
/**
129
 * Implements hook_block_configure().
130
 */
131
function cdm_taxontree_block_configure($delta) {
132
  // TODO Rename block deltas (e.g. '1') to readable strings.
133
  if (TRUE) {
134
    switch ($delta) {
135
      case '1':
136
        $vocs = taxonomy_get_vocabularies();
137
        $options = array();
138
        foreach ($vocs as $voc) {
139
          $options[$voc->vid] = $voc->name;
140
        }
141
        $form['vid'] = array(
142
          '#type' => 'select',
143
          '#title' => t('Category'),
144
          '#default_value' => variable_get('cdm_taxontree_block_1_vid', 0),
145
          '#options' => $options,
146
        );
147
        return $form;
148
    }
149
  }
150
}
151

    
152
/**
153
 * Implements hook_block_save().
154
 */
155
function cdm_taxontree_block_save($delta, $edit) {
156
  // TODO Rename block deltas (e.g. '1') to readable strings.
157
  if (TRUE) {
158
    switch ($delta) {
159
      case '1':
160
        variable_set('cdm_taxontree_block_1_vid', $edit['vid']);
161
        return;
162
    }
163
  }
164
}
165

    
166
/**
167
 * Implements hook_help().
168
 */
169
function cdm_taxontree_help($path, $arg) {
170
  switch ($path) {
171
    case 'admin/modules#description':
172
      return t('Defines a selection widget for massive taxonomy structures.');
173
  }
174
}
175

    
176
/**
177
 * Implements hook_field_info().
178
 */
179
function cdm_taxontree_field_info() {
180
  return array(
181
    'cdm_taxontree' => array('label' => 'CDM Taxontree'),
182
  );
183
}
184

    
185
/**
186
 * @todo Please document this function.
187
 * @see http://drupal.org/node/1354
188
 */
189
function cdm_taxontree_field_formatter_info() {
190
  return array(
191
    'default' => array(
192
      'label' => t('Default'),
193
      'field types' => array('cdm_taxontree'),
194
    ),
195
    'link' => array(
196
      'label' => t('With link'),
197
      'field types' => array('cdm_taxontree'),
198
    ),
199
  );
200
}
201

    
202
/**
203
 * Formatters to prepare the correct links for taxa.
204
 */
205
function cdm_taxontree_field_formatter($field, $item, $formatter, $node) {
206
  switch ($formatter) {
207
    case 'link':
208
      $term = taxonomy_term_load($item['tid']);
209
      $taxa = db_query('SELECT name FROM {taxonomy_vocabulary} WHERE vid = :vid', array(':vid' => $term->vid))->fetchField();
210
      switch ($taxa) {
211
        case 'Taxonomy':
212
          $link = 'interest_by_taxonomy/';
213
          break;
214

    
215
        case 'Georegion':
216
          $link = 'interest_by_georegion/';
217
          break;
218

    
219
        default:
220
          $link = 'taxonomy/term/';
221
      }
222
      return l($term->name, $link . $term->tid);
223

    
224
    default:
225
      $name = db_query('SELECT name FROM {taxonomy_term_data} WHERE tid = :tid', array(':tid' => $item['tid']))->fetchField();
226
      return $name;
227
  }
228
}
229

    
230

    
231
/**
232
 * Transforms an unpredictably and irregularly nested set of tids (as returned
233
 * from a taxonomy form) into a linear array of tids.
234
 * borrow from taxonomy_browser.module
235
 */
236
function _cdm_taxontree_get_all_children($tids = NULL, $include_children = FALSE) {
237
  static $tid_list = array();
238

    
239
  if (isset($tids) && is_array($tids)) {
240

    
241
    foreach ($tids as $key => $tid) {
242
      if (!empty($tid)) {
243
        if (is_array($tid)) {
244
          foreach ($tid as $key2 => $tid2) {
245
            if (!empty($tid2)) {
246
              $tid_list[$tid2] = $tid2;
247
            }
248
          }
249
        }
250
        else {
251
          $tid_list[$tid] = $tid;
252
        }
253
      } /* end !empty */
254
    } /* end foreach */
255
  }
256

    
257
  if ($include_children) {
258
    foreach ($tid_list as $tid) {
259
      _cdm_taxontree_get_children($tid_list, $tid);
260
    }
261
  }
262

    
263
  return $tid_list;
264
}
265

    
266
/**
267
 * @todo Please document this function.
268
 * @see http://drupal.org/node/1354
269
 */
270
function _cdm_taxontree_get_children(&$tid_list, $tid) {
271
  $child_nodes = taxonomy_get_children($tid);
272
  if (!empty($child_nodes)) {
273
    foreach ($child_nodes as $child_tid => $child_term) {
274
      $tid_list[$tid] = $tid;
275
      _cdm_taxontree_get_children($tid_list, $child_tid);
276
    }
277
  }
278
  else {
279
    $tid_list[$tid] = $tid;
280
  }
281
}
282

    
283
/**
284
 * @todo Please document this function.
285
 * @see http://drupal.org/node/1354
286
 */
287
function cdm_taxontree_set($key, $value) {
288
  if (is_string($key)) {
289
    $_SESSION['cdm']['taxontree'][$key] = $value;
290
  }
291

    
292
  if ($_GET['destination']) {
293
    $destination = $_GET['destination'];
294
    unset($_GET['destination']);
295
    drupal_goto($destination);
296
  }
297
}
298

    
299
/**
300
 * Enter description here...
301
 *
302
 * @param string $secUuid
303
 *
304
 * @return unknown
305
 */
306
function cdm_taxontree_secRefTitle_for($secUuid) {
307

    
308
  $reference = cdm_api_secref_cache_get($secUuid);
309
  if ($reference) {
310
    $cit = $reference->titleCache;
311
  }
312
  else {
313
    $cit = '[no title for:' . $secUuid . ']';
314
  }
315
  return $cit;
316
}
317

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

    
352
/**
353
 * Enter description here...
354
 *
355
 * @return unknown
356
 */
357
function _get_compact_mode() {
358
  if (!isset($_SESSION['cdm']['taxontree']['compact_mode'])) {
359
    $_SESSION['cdm']['taxontree']['compact_mode'] = 'expanded';
360
  }
361
  return $_SESSION['cdm']['taxontree']['compact_mode'];
362
}
363

    
364
/**
365
 * Converts Drupal taxonomy terms into preliminary cdm tree nodes.
366
 *
367
 * An array of drupal taxonomy terms are converted into an
368
 * array of partially instantiated cdm tree nodes by adding the fields
369
 * relevant for tree node processing in cdm_taxontree.
370
 *
371
 * term => cdm tree node
372
 * ------------------------------------
373
 * tid -> uuid
374
 * name -> titleCache
375
 * taggedName
376
 * secUuid
377
 * isAccepted
378
 * taxonomicChildrenCount
379
 * alternativeConceptRefs
380
 *
381
 * @param array $terms
382
 */
383
function cdm_taxontree_terms2treenodes(&$terms) {
384
  foreach ($terms as &$term) {
385
    $term->uuid = $term->tid;
386
    $term->titleCache = $term->name;
387
    $term->taxonomicChildrenCount = count(taxonomy_get_children($term->tid, $term->vid));
388
  }
389
  return $terms;
390
}
391

    
392
/**
393
 * Enter description here...
394
 *
395
 * @param unknown_type $tid
396
 * @param unknown_type $vid
397
 * @param unknown_type $theme
398
 */
399
function cdm_taxontree_taxonomy_children($tid, $vid, $theme) {
400
  $args = func_get_args();
401
  $tid = array_shift($args);
402
  $vid = array_shift($args);
403
  $theme = array_shift($args);
404

    
405
  $children = cdm_taxontree_get_children($tid, $vid);
406
  $children = cdm_taxontree_terms2treenodes($children);
407
  array_unshift($args, $theme, $children);
408
  print call_user_func_array('theme', $args);
409
}
410

    
411
/**
412
 * @todo document this function.
413
 */
414
function cdm_taxontree_get_root($vid = NULL) {
415
  if (is_numeric($vid)) {
416
    // vid, $parent = 0, $depth = -1, $max_depth = NULL) {
417
    $terms = taxonomy_get_tree($vid, 0, 1);
418
    return cdm_taxontree_terms2treenodes($terms);
419
  }
420
  else {
421
    return cdm_ws_taxonomy();
422
  }
423
}
424

    
425
/**
426
 * @todo Enter description here...
427
 *
428
 * @param unknown_type $uuid
429
 * @param unknown_type $vid
430
 *
431
 * @return unknown
432
 */
433
function cdm_taxontree_get_children($uuid, $vid = NULL) {
434

    
435
  if (is_numeric($vid)) {
436
    $terms = taxonomy_get_children($uuid, $vid);
437
    return cdm_taxontree_terms2treenodes($terms);
438
  }
439
  else {
440
    // FIXME Replace $uuid by path of parent $uuids.
441
    return cdm_ws_taxonomy($uuid);
442
  }
443
}
444

    
445
/**
446
 * @todo Enter description here...
447
 *
448
 * @param unknown_type $uuid
449
 *
450
 * @return unknown
451
 */
452
function cdm_taxontree_get_parents($uuid) {
453

    
454
  if (!is_uuid($uuid)) {
455
    // Using Drupal taxonomy.
456
    $terms = taxonomy_get_parents($uuid);
457
    array_push($terms, taxonomy_term_load($uuid));
458
    $terms = array_reverse($terms);
459
    return cdm_taxontree_terms2treenodes($terms);
460
  }
461
  else {
462
    // Using cdm.
463
    $terms = cdm_ws_taxonomy_pathFromRoot($uuid);
464
    if (!$terms) {
465
      return;
466
    }
467
    $terms = array_reverse($terms);
468
    return $terms;
469
  }
470
}
471

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

    
491
    $compact_tree = cdm_taxontree_filters_active() && _get_compact_mode() != 'expanded';
492
  }
493
  // Valid compact_modes: 'expanded', 'compact', 'flattened'.
494
  // Get the root level.
495
  $root_tree = cdm_taxontree_get_root($vid);
496
  /*
497
  if(!$root_tree || !is_array($root_tree)){
498
    return array();
499
  }
500
  */
501
  $root_tree = _cdm_resultset2nodelist($root_tree, cdm_taxontree_filters_active());
502

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

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

    
523
   // Reorder siblings & populate expanded nodes with children and propagate
524
   // the filter attribute.
525
   $root_tree = cdm_taxontree_populate($root_tree, $compact_tree === FALSE);
526

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

    
541
  return $root_tree;
542
}
543

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

    
564
  $branch_path = array();
565

    
566
  $parents = cdm_taxontree_get_parents($taxonUuid);
567
  if (!$parents) {
568
    if ($is_filter_path) {
569
      // \remove invalid filter.
570
      cdm_taxontree_filters_remove($taxonUuid);
571
    }
572
    return FALSE;
573
  }
574

    
575
  $parents = _cdm_resultset2nodelist($parents, NULL);
576
  $lastParent = NULL;
577

    
578
  foreach ($parents as $pnode) {
579
    $pnode->focused = FALSE;
580

    
581
    // TODO to be replaced by ($pnode->taxonUuid == $taxonUuid); ??
582
    // compare usage of $is_focused.
583
    if ($lastParent) {
584
      $pnode->children = array($lastParent->taxonUuid => $lastParent);
585
      if (!is_null($is_filter_path)) {
586
        $pnode->filter = ($is_filter_path ? 'excludes' : 'included');
587
      }
588
      if (!is_null($is_expanded)) {
589
        $pnode->expanded = ($is_expanded ? 'expanded' : 'collapsed');
590
      }
591
    }
592
    else {
593
      // The uppermost node of branch.
594
      if (!is_null($is_filter_path)) {
595
        $pnode->filter = ($is_filter_path ? 'on' : 'includes');
596
      }
597
      // Uppermost node is always expanded if it has children.
598
      $pnode->focused = $is_focused;
599
      $pnode->expanded = ($pnode->taxonomicChildrenCount ? 'expanded' : 'collapsed');
600
    }
601
    $lastParent = $pnode;
602
  }
603
  $branch_path[$pnode->taxonUuid] = $pnode;
604
  return $branch_path;
605
}
606

    
607
/**
608
 * Order a tree and populate expanded nodes.
609
 *
610
 * Performs two steps on each level of the tree:
611
 *  1. Reorder siblings except root (which is expected to be ordered already)
612
 *     alphabetically.
613
 *  2. Populate children of expanded nodes  & propagate the filter attribute
614
 *
615
 * @param array $tree
616
 * @param unknown $expand_excluded
617
 * @param unknown $filter_default
618
 *
619
 * @return unknown
620
 */
621
function cdm_taxontree_populate($tree, $expand_excluded, $filter_default = NULL) {
622

    
623
  if (!is_array($tree)) {
624
    return FALSE;
625
  }
626
  foreach (array_keys($tree) as $uuid) {
627

    
628
    if (!isset($tree[$uuid]->filter) && !is_null($filter_default)) {
629
      $tree[$uuid]->filter = $filter_default;
630
    }
631

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

    
634
      if (isset($tree[$uuid]->vid)) {
635
        $children = cdm_taxontree_get_children($uuid, $tree[$uuid]->vid);
636
      }
637
      else {
638
        $children = cdm_taxontree_get_children($uuid);
639
      }
640
      $children = _cdm_resultset2nodelist($children, ($tree[$uuid]->filter == 'excludes'));
641

    
642
      // Store the children of the node for later processing.
643
      if (isset($tree[$uuid]->children) && is_array($tree[$uuid]->children)) {
644
        $pnode_children = $tree[$uuid]->children;
645
      }
646
      else {
647
        $pnode_children = FALSE;
648
      }
649
      // Replace the children by the newly retrieved child nodes.
650
      $tree[$uuid]->children = $children;
651

    
652
      if ($pnode_children) {
653
        // Recurse into the childtree which was stored before.
654
        $pnode_children = cdm_taxontree_populate($pnode_children, $expand_excluded, $tree[$uuid]->filter);
655
        // Recombine.
656
        foreach ($pnode_children as $childUuid => $cnode) {
657
          $tree[$uuid]->children[$childUuid] = $cnode;
658
        }
659
      }
660
    }
661
    else {
662
      // Reorder nodes which are not expanded, expanded nodes are reordered
663
      // implicitly above.
664
      if (isset($tree[$uuid]->children) && count($tree[$uuid]->children) > 1) {
665
        // Copy the children into an array which can be sorted by its keys.
666
        $ordered = array();
667
        foreach ($tree[$uuid]->children as $cnode) {
668
          // Concatenate full name and uid.
669
          $reordered[str_pad($cnode->titleCache, 255, '-') . $cnode->taxonUuid] = $cnode;
670
        }
671

    
672
        // Sort.
673
        ksort($reordered);
674

    
675
        // Move the children back into the parent node.
676
        $tree[$uuid]->children = array();
677
        foreach ($reordered as $cnode) {
678
          $tree[$uuid]->children[$cnode->taxonUuid] = $cnode;
679
        }
680
      }
681
      if (!isset($tree[$uuid]->children)) {
682
        $tree[$uuid]->children = FALSE;
683
      }
684
      $tree[$uuid]->children = cdm_taxontree_populate($tree[$uuid]->children, $expand_excluded, $tree[$uuid]->filter);
685
    }
686
  }
687
  return $tree;
688
}
689

    
690
/**
691
 * Enter description here...
692
 *
693
 * @param array $tree
694
 *   Tree to flatten.
695
 * @param array $new_root
696
 *
697
 * @return unknown
698
 */
699
function cdm_taxontree_flatten($tree, &$new_root = NULL) {
700
  if (empty($new_root)) {
701
    $new_root = array();
702
  }
703
  foreach ($tree as $node) {
704
    if ($node->filter == 'on') {
705
      $new_root[$node->taxonUuid] = $node;
706
    }
707
    elseif (is_array($node->children)) {
708
      cdm_taxontree_flatten($node->children, $new_root);
709
    }
710
  }
711
  return $new_root;
712
}
713

    
714
/**
715
 * Merge a branch into a tree.
716
 *
717
 * Merge a branch into a tree whereas the tree dominated the branch except
718
 * nodes having property filter set to "on". These always dominate
719
 * nevertheless if they are in tree or branch.
720
 *
721
 * @param array $tree
722
 *   The dominant tree.
723
 * @param array $branch
724
 *   The tree to be merged in.
725
 *
726
 * @return array
727
 *   The merged $tree.
728
 */
729
function _cdm_taxontree_merge($tree, $branch) {
730

    
731
  if (!$branch || !is_array($branch)) {
732
    return $tree;
733
  }
734

    
735
  if (!is_array($tree)) {
736
    return;
737
  }
738

    
739
  foreach (array_keys($tree) as $uuid) {
740
    // Check if node exists in $branch.
741
    if (!empty($branch[$uuid])) {
742
      // Preserve filter property.
743
      if (isset($tree[$uuid]->filter) && !(isset($branch[$uuid]->filter) && $branch[$uuid]->filter == 'on')) {
744
        $branch[$uuid]->filter = $tree[$uuid]->filter;
745
      }
746
      elseif (isset($branch[$uuid]->filter)) {
747
        $tree[$uuid]->filter = $branch[$uuid]->filter;
748
      }
749
      // Preserve expanded property.
750
      if (isset($tree[$uuid]->expanded)) {
751
        $branch[$uuid]->expanded = $tree[$uuid]->expanded;
752
      }
753
      elseif (isset($branch[$uuid]->expanded)) {
754
        $tree[$uuid]->expanded = $branch[$uuid]->expanded;
755
      }
756
      // Preserve focused property.
757
      if (isset($tree[$uuid]->focused)) {
758
        $branch[$uuid]->focused = $tree[$uuid]->focused;
759
      }
760
      elseif (isset($branch[$uuid]->focused)) {
761
        $tree[$uuid]->focused = $branch[$uuid]->focused;
762
      }
763
      // $Uuid exists check if the node in tree1 or tree2 contains children.
764
      if (isset($branch[$uuid]->children) && is_array($branch[$uuid]->children) && isset($tree[$uuid]->children) && is_array($tree[$uuid]->children)) {
765
        // Merge recursive.
766
        $tree[$uuid]->children = _cdm_taxontree_merge($tree[$uuid]->children, $branch[$uuid]->children);
767
      }
768
      elseif (isset($branch[$uuid]->children) && is_array($branch[$uuid]->children)) {
769
        $tree[$uuid] = $branch[$uuid];
770
      }
771
      unset($branch[$uuid]);
772
    }
773
  }
774
  // Append remaining items from branch to tree.
775
  foreach (array_keys($branch) as $uuid) {
776
    $tree[$uuid] = $branch[$uuid];
777
  }
778
  return $tree;
779
}
780

    
781

    
782
/**
783
 * Alter a reultset into an array of TreeNode instances with taxonUuid as keys.
784
 *
785
 * Replaces the keys of an array of TreeNode instances
786
 * by the $treenode->taxonUuid of the single array elements and sets
787
 * additional fields.
788
 *
789
 * @param array $resultset
790
 *   Array of TreeNode instances as +returned by the cdm web service.
791
 * @param mixed $excluded
792
 *   Whether the $resultset is included by a active filter. Is ignored if NULL.
793
 * @param mixed $expanded
794
 *   Whether the children of the nodes in the $resultset are expanded or not.
795
 *   Is ignored if NULL.
796
 *
797
 * @return array
798
 *   A tree of TreeNode instances with altered keys.
799
 */
800
function _cdm_resultset2nodelist($resultset, $excluded = NULL, $expanded = NULL) {
801

    
802
  if (!is_array($resultset)) {
803
    return FALSE;
804
  }
805

    
806
  $tree = array();
807
  foreach ($resultset as $treeNode) {
808
    if (!is_null($excluded)) {
809
      $treeNode->filter = ($excluded ? 'excluded' : 'included');
810
    }
811
    if (!is_null($expanded)) {
812
      $treeNode->expanded = ($expanded ? 'expanded' : 'collapsed');
813
    }
814
    $tree[$treeNode->taxonUuid] = $treeNode;
815
  }
816
  return $tree;
817
}
818

    
819

    
820
// ------------------------ THEME --------------------------- //
821
/**
822
 * @todo Please document this function.
823
 * @see http://drupal.org/node/1354
824
 */
825
function theme_cdm_taxontree_add_scripts() {
826
  $path_cdm_taxontree = drupal_get_path('module', 'cdm_taxontree');
827
  $path_preferred_module = drupal_get_path('module', 'cdm_dataportal') ? drupal_get_path('module', 'cdm_dataportal') : $path_cdm_taxontree;
828
  drupal_add_css($path_cdm_taxontree . '/cdm_taxontree.css');
829
  drupal_add_js($path_preferred_module . '/js/jquery.dimensions.js');
830
  drupal_add_js($path_cdm_taxontree . '/js/cdm_taxontree.js');
831
  drupal_add_js($path_cdm_taxontree . '/js/jquery.scrollTo.js');
832
}
833

    
834

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
984
    $show_filter_switch = FALSE;
985

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

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

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

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

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

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

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

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

    
1056
  return $out;
1057
}
1058

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

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

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

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

    
1084
  }
1085

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

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

    
1098
  return $out;
1099
}
1100

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

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

    
1128
/**
1129
 * @todo Please document this function.
1130
 * @see http://drupal.org/node/1354
1131
 */
1132
function cdm_taxontree_theme() {
1133
  return array(
1134
    'cdm_taxontree' => array('variables' => array(
1135
      'tree' => NULL,
1136
      'filterIncludes' => NULL,
1137
      'show_filter_switch' => FALSE,
1138
      'tree_node_callback' => FALSE,
1139
      'element_name' => FALSE,
1140
      )),
1141
    'cdm_taxontree_add_scripts' => array('variables' => array()),
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)