Project

General

Profile

Download (93.8 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
  /**
3
   * @file
4
   * Required or useful functions for using CDM Data Store Webservices.
5
   *
6
   * Naming conventions:
7
   * ----------------------
8
   * - All webservice access methods are prefixed with cdm_ws.
9
   *
10
   * @copyright
11
   *   (C) 2007-2012 EDIT
12
   *   European Distributed Institute of Taxonomy
13
   *   http://www.e-taxonomy.eu
14
   *
15
   *   The contents of this module are subject to the Mozilla
16
   *   Public License Version 1.1.
17
   * @see http://www.mozilla.org/MPL/MPL-1.1.html
18
   *
19
   * @author
20
   *   - Andreas Kohlbecker <a.kohlbecker@BGBM.org>
21
   *   - Wouter Addink <w.addink@eti.uva.nl> (migration from Drupal 5 to Drupal7)
22
   */
23

    
24
  module_load_include('php', 'cdm_api', 'xml2json');
25
  module_load_include('php', 'cdm_api', 'commons');
26
  module_load_include('php', 'cdm_api', 'uuids');
27
  module_load_include('php', 'cdm_api', 'enums');
28
  module_load_include('php', 'cdm_api', 'webservice_uris');
29
  module_load_include('php', 'cdm_api', 'cdm_node');
30
  module_load_include('inc', 'cdm_api', 'tagged_text');
31

    
32
  /**
33
   * Timeout used to override the default of 30 seconds
34
   * in @see drupal_http_request()
35
   *
36
   * @var CDM_HTTP_REQUEST_TIMEOUT: A float representing the maximum number of seconds the function
37
   *     call may take
38
   */
39
  define('CDM_HTTP_REQUEST_TIMEOUT', 90.0);
40

    
41

    
42

    
43
/**
44
 * orderBy webservice query parameter value
45
 */
46
define('CDM_ORDER_BY_ID_ASC', 'BY_ID_ASC');
47

    
48
/**
49
 * orderBy webservice query parameter value
50
 */
51
define('CDM_ORDER_BY_ID_DESC', 'BY_ID_DESC');
52
/**
53
 * orderBy webservice query parameter value
54
 */
55
define('CDM_ORDER_BY_TITLE_CACHE_ASC', 'BY_TITLE_CACHE_ASC');
56
/**
57
 * orderBy webservice query parameter value
58
 */
59
define('CDM_ORDER_BY_TITLE_CACHE_DESC', 'BY_TITLE_CACHE_DESC');
60
/**
61
 * orderBy webservice query parameter value
62
 */
63
define('CDM_ORDER_BY_NOMENCLATURAL_ORDER_ASC', 'BY_NOMENCLATURAL_ORDER_ASC');
64
/**
65
 * orderBy webservice query parameter value
66
 */
67
define('CDM_ORDER_BY_NOMENCLATURAL_ORDER_DESC', 'BY_NOMENCLATURAL_ORDER_DESC');
68
/**
69
 * orderBy webservice query parameter value
70
 */
71
define('CDM_ORDER_BY_ORDER_INDEX_ASC', 'BY_ORDER_INDEX_ASC');
72
/**
73
 * orderBy webservice query parameter value
74
 */
75
define('CDM_ORDER_BY_ORDER_INDEX_DESC', 'BY_ORDER_INDEX_DESC');
76

    
77

    
78
/**
79
 * Implements hook_menu().
80
 */
81
function cdm_api_menu() {
82
  $items = array();
83

    
84
  // usage: url('cdm_api/proxy/'.urlencode($content_url)."/$theme");
85
  $items['cdm_api/proxy'] = array(
86
    'page callback' => 'proxy_content',
87
    'access arguments' => array(
88
      'access cdm content',
89
    ),
90
    'type' => MENU_CALLBACK,
91
  );
92

    
93
  $items['cdm_api/setvalue/session'] = array(
94
    'page callback' => 'setvalue_session',
95
    'access arguments' => array(
96
      'access cdm content',
97
    ),
98
    'type' => MENU_CALLBACK,
99
  );
100

    
101
  return $items;
102
}
103

    
104
/**
105
 * info hook implementation for the CDM web service debug block
106
 *
107
 * Implements hook_block_info().
108
 *
109
 * @see \cdm_api_block_view()
110
 */
111
function cdm_api_block_info() {
112

    
113
  $block['cdm_ws_debug'] = array(
114
      "info" => t("CDM web service debug"),
115
      "cache" => DRUPAL_NO_CACHE
116
  );
117
  return $block;
118
}
119

    
120
/**
121
 * block_view hook implementation for the CDM web service debug block
122
 *
123
 * Implements hook_block_view().
124
 *
125
 * @see  cdm_api_block_info()
126
 */
127
function cdm_api_block_view($delta) {
128
  switch ($delta) {
129
    case 'cdm_ws_debug':
130

    
131
    $cdm_ws_url = cdm_webservice_url();
132

    
133
    $field_map = array(
134
        'ws_uri' => t('URI') . ' <code>(' . $cdm_ws_url .'...)</code>',
135
        'time' => t('Time'),
136
        'fetch_seconds' => t('Fetching [s]'),
137
        'parse_seconds' => t('Parsing [s]'),
138
        'size_kb' => t('Size [kb]'),
139
        'status' => t('Status'),
140
        'data_links' =>  t('Links'),
141
    );
142

    
143

    
144
    if (!isset($_SESSION['cdm']['ws_debug'])) {
145
      $_SESSION['cdm']['ws_debug'] = array();
146
    }
147

    
148
    $header = '<thead><tr><th>' . join('</th><th>' , array_values($field_map)) . '</th></thead>';
149
    $footer = '<tfoot><tr><th>' . join('</th><th>' , array_values($field_map)) . '</th></tfoot>';
150
    $rows = array();
151

    
152
    foreach ($_SESSION['cdm']['ws_debug'] as $data){
153

    
154
      $data = unserialize($data);
155

    
156
      // stip of webservice base url
157
      $data['ws_uri'] = str_replace($cdm_ws_url, '', $data['ws_uri']);
158
      if($data['method'] == 'POST'){
159
        $data['ws_uri'] = 'POST: ' . $data['ws_uri'] . '?' . $data['post_data'];
160
      }
161

    
162
      $cells = array();
163
      foreach ($field_map as $field => $label){
164
        $cells[] = '<td class="' . $field . '">' .  $data[$field] . '</td>';
165
      }
166
      $rows[] = '<tr class="' . $data['status']  . '">' . join('' , $cells). '</tr>';
167
    }
168
    // clear session again
169
    $_SESSION['cdm']['ws_debug'] = array();
170

    
171
    _add_js_ws_debug();
172

    
173
    $block['subject'] = ''; // no subject, title in content for having a defined element id
174
    // otherwise it would depend on the theme
175
    $block['content'] = array(
176
      '#markup' => '<h4 id="cdm-ws-debug-button">' . t('CDM Debug') . '</h4>'
177
        // cannot use theme_table() since table footer is not jet supported in D7
178
        . '<div id="cdm-ws-debug-table-container"><table id="cdm-ws-debug-table">'
179
        . $header
180
        . '<tbody>' . join('', $rows) . '</tbody>'
181
        . $footer
182
        . '</table></div>',
183
      '#attached' => array(
184
        'css' => array(
185
          drupal_get_path('module', 'cdm_dataportal') . '/cdm_dataportal_ws_debug.css'
186
        )
187
      )
188
    );
189
    return $block;
190
  }
191
}
192

    
193
/**
194
 * Implements hook_cron().
195
 *
196
 * Expire outdated cache entries.
197
 */
198
function cdm_api_cron() {
199
  cache_clear_all(NULL, 'cache_cdm_ws');
200
}
201

    
202
/**
203
 * Lists the classifications a taxon belongs to
204
 *
205
 * @param CDM type Taxon $taxon
206
 *   the taxon
207
 *
208
 * @return array
209
 *   aray of CDM instances of Type Classification
210
 */
211
function get_classifications_for_taxon($taxon) {
212

    
213
  return cdm_ws_get(CDM_WS_TAXON_CLASSIFICATIONS, $taxon->uuid);
214
}
215

    
216
/**
217
 * Returns the chosen FeatureTree to be used as FeatureTree for the taxon profile.
218
 *
219
 * The FeatureTree returned is the term tree one that has been set in the
220
 * dataportal settings (layout->taxon:profile).
221
 * When the chosen FeatureTree is not found in the database,
222
 * the standard feature tree (UUID_DEFAULT_FEATURETREE) will be returned.
223
 *
224
 * @return object
225
 *   A cdm TermTree object.
226
 */
227
function get_profile_feature_tree() {
228
  static $profile_featureTree;
229

    
230
  if($profile_featureTree == NULL) {
231
    $profile_featureTree = cdm_ws_get(
232
      CDM_WS_TERMTREE,
233
      variable_get(CDM_PROFILE_FEATURETREE_UUID, UUID_DEFAULT_FEATURETREE)
234
    );
235
    if (!$profile_featureTree) {
236
      $profile_featureTree = cdm_ws_get(CDM_WS_TERMTREE, UUID_DEFAULT_FEATURETREE);
237
    }
238
  }
239

    
240
  return $profile_featureTree;
241
}
242

    
243
/**
244
 * Returns the chosen TermTree to be used as FeatureTree for SpecimenDescriptions.
245
 *
246
 * The TermTree returned is the one that has been set in the
247
 * dataportal settings (layout->taxon:specimen).
248
 * When the chosen TermTree is not found in the database,
249
 * the standard term tree (UUID_DEFAULT_TERMTREE) will be returned.
250
 *
251
 * @return object
252
 *   A cdm TermTree object.
253
 */
254
function cdm_get_occurrence_featureTree() {
255
  static $occurrence_featureTree;
256

    
257
  if($occurrence_featureTree == NULL) {
258
    $occurrence_featureTree = cdm_ws_get(
259
      CDM_WS_TERMTREE,
260
      variable_get(CDM_OCCURRENCE_FEATURETREE_UUID, UUID_DEFAULT_FEATURETREE)
261
    );
262
    if (!$occurrence_featureTree) {
263
      $occurrence_featureTree = cdm_ws_get(CDM_WS_TERMTREE, UUID_DEFAULT_FEATURETREE);
264
    }
265
  }
266
  return $occurrence_featureTree;
267
}
268

    
269
/**
270
 * Returns the FeatureTree for structured descriptions
271
 *
272
 * The FeatureTree returned is the one that has been set in the
273
 * dataportal settings (layout->taxon:profile).
274
 * When the chosen FeatureTree is not found in the database,
275
 * the standard feature tree (UUID_DEFAULT_FEATURETREE) will be returned.
276
 *
277
 * @return mixed
278
 *   A cdm FeatureTree object.
279
 */
280
function get_structured_description_featureTree() {
281
  static $structured_description_featureTree;
282

    
283
  if($structured_description_featureTree == NULL) {
284
    $structured_description_featureTree = cdm_ws_get(
285
        CDM_WS_TERMTREE,
286
        variable_get(CDM_DATAPORTAL_STRUCTURED_DESCRIPTION_FEATURETREE_UUID, UUID_DEFAULT_FEATURETREE)
287
    );
288
    if (!$structured_description_featureTree) {
289
      $structured_description_featureTree = cdm_ws_get(
290
          CDM_WS_TERMTREE,
291
          UUID_DEFAULT_FEATURETREE
292
      );
293
    }
294
  }
295
  return $structured_description_featureTree;
296
}
297

    
298

    
299
/**
300
 * @todo Please document this function.
301
 * @see http://drupal.org/node/1354
302
 */
303
function set_last_taxon_page_tab($taxonPageTab) {
304
  $_SESSION['cdm']['taxon_page_tab'] = $taxonPageTab;
305
}
306

    
307
/**
308
 * @todo Please document this function.
309
 * @see http://drupal.org/node/1354
310
 */
311
function get_last_taxon_page_tab() {
312
  if (isset($_SESSION['cdm']['taxon_page_tab'])) {
313
    return $_SESSION['cdm']['taxon_page_tab'];
314
  }
315
  else {
316
    return FALSE;
317
  }
318
}
319

    
320
/**
321
 * NOTE: The cdm-library provides a very similar server side function. See
322
 * eu.etaxonomy.cdm.model.media.MediaUtils.filterAndOrderMediaRepresentations()
323
 *
324
 * @param object $media
325
 * @param array $mime_types
326
 *    an array of mimetypes in their order of preference. e.g:
327
 *    array('application/pdf', 'image/png', 'image/jpeg', 'image/gif', 'text/html')
328
 * @param int $width
329
 *    The width of the optimal image. If null, the method will return the representation with the biggest expansion
330
 * @param int $height
331
 *    The height of the optimal image. If null, the method will return the representation with the biggest expansion
332
 * @param bool $mime_types_filter_excludes
333
 *    Toggles the mode of the $mime_types filter to exclude when set to TRUE
334
 * @param bool $strict_size_limit
335
 *    Width an height parameters are treated as maximum values when this parameter is enabled.
336
 * @return array
337
 *   An array with preferred media representations or else an empty array.
338
 */
339
function cdm_preferred_media_representations($media, array $mime_types, $width = NULL, $height = NULL, $mime_types_filter_excludes = FALSE, $strict_size_limit = FALSE) {
340
  $matching_representations_by_rank = [];
341
  if (!isset($media->representations[0])) {
342
    return $matching_representations_by_rank;
343
  }
344

    
345

    
346

    
347
    foreach ($media->representations as &$representation) {
348
      // If the mimetype is not known, try inferring it.
349
      if (!$representation->mimeType) {
350
        if (isset($representation->parts[0])) {
351
          $representation->mimeType = infer_mime_type($representation->parts[0]->uri);
352
        }
353
      }
354
      $mime_type_match = array_search($representation->mimeType, $mime_types) !== false;
355
      $match_per_mime_type_filter = $mime_types_filter_excludes ? !$mime_type_match : $mime_type_match;
356
      if ($match_per_mime_type_filter) {
357
        $expansion_delta_sum = 0;
358
        $valid_parts_cnt = 0;
359
        // Look for part with the best matching size.
360
        foreach ($representation->parts as $part) {
361
          if (empty($part->uri)) {
362
            // skip part if URI is missing
363
            continue;
364
          }
365
          if($strict_size_limit){
366
            if (isset($part->width) && isset($part->height) && $part->width <= $width && $part->height <= $height) {
367
              $valid_parts_cnt++;
368
            }
369
          } else {
370
            // calculate expansion delta
371
            $valid_parts_cnt++;
372
            $expansion_delta = PHP_INT_MAX; // biggest delta for unknown sizes
373

    
374
            // determine the optimal size
375
            if (isset($part->width) && isset($part->height)) {
376
              $expansion = $part->width * $part->height;
377
              if ($width != null && $height != null) {
378
                $optimalExpansion = $height * $width;
379
              } else {
380
                $optimalExpansion = PHP_INT_MAX;
381
              }
382
              // determine the difference
383
              $expansion_delta = $expansion > $optimalExpansion ? $expansion - $optimalExpansion : $optimalExpansion - $expansion;
384
            }
385
            // sum up the expansionDeltas of all parts contained in the representation
386
            $expansion_delta_sum += $expansion_delta;
387
          }
388
        }
389
        if($valid_parts_cnt > 0){
390
          $expansion_delta_sum = $expansion_delta_sum / $valid_parts_cnt;
391
          $matching_representations_by_rank[$expansion_delta_sum] = $representation;
392
        }
393
      }
394

    
395
    }
396

    
397
  // Sort the array so that the smallest key value is the first in the array
398
  ksort($matching_representations_by_rank);
399
  return $matching_representations_by_rank;
400
}
401

    
402
/**
403
 * Infers the mime type of a file using the filename extension.
404
 *
405
 * The filename extension is used to infer the mime type.
406
 *
407
 * @param string $filepath
408
 *   The path to the respective file.
409
 *
410
 * @return string
411
 *   The mimetype for the file or FALSE if the according mime type could
412
 *   not be found.
413
 */
414
function infer_mime_type($filepath) {
415
  static $mimemap = NULL;
416
  if (!$mimemap) {
417
    $mimemap = array(
418
      'jpg' => 'image/jpeg',
419
      'jpeg' => 'image/jpeg',
420
      'png' => 'image/png',
421
      'gif' => 'image/gif',
422
      'giff' => 'image/gif',
423
      'tif' => 'image/tif',
424
      'tiff' => 'image/tif',
425
      'pdf' => 'application/pdf',
426
      'html' => 'text/html',
427
      'htm' => 'text/html',
428
    );
429
  }
430
  $extension = substr($filepath, strrpos($filepath, '.') + 1);
431
  if (isset($mimemap[$extension])) {
432
    return $mimemap[$extension];
433
  }
434
  else {
435
    // FIXME remove this hack just return FALSE;
436
    return 'text/html';
437
  }
438
}
439

    
440
/**
441
 * Formats a mysql datatime as string
442
 *
443
 * @param $datetime
444
 * @param string $format
445
 *
446
 * @return
447
 *  the formatted string representation of the $datetime
448
 */
449
function format_datetime($datetime, $format = 'Y-m-d H:i:s O', $strip_zeros = true){
450
  return date($format, strtotime($datetime));
451
}
452

    
453
/**
454
 * Converts an ISO 8601 org.joda.time.Partial to a year.
455
 *
456
 * The function expects an ISO 8601 time representation of a
457
 * org.joda.time.Partial of the form yyyy-MM-dd.
458
 *
459
 * @param string $partial
460
 *   ISO 8601 time representation of a org.joda.time.Partial.
461
 *
462
 * @return string
463
 *   Returns the year. In case the year is unknown (= ????), it returns NULL.
464
 */
465
function partialToYear($partial) {
466
  if (is_string($partial)) {
467
    $year = drupal_substr($partial, 0, 4);
468
    if (preg_match("/[0-9][0-9][0-9][0-9]/", $year)) {
469
      return $year;
470
    }
471
  }
472
  return '';
473
}
474

    
475
/**
476
 * Converts an ISO 8601 org.joda.time.Partial to a month.
477
 *
478
 * This function expects an ISO 8601 time representation of a
479
 * org.joda.time.Partial of the form yyyy-MM-dd.
480
 * In case the month is unknown (= ???) NULL is returned.
481
 *
482
 * @param string $partial
483
 *   ISO 8601 time representation of a org.joda.time.Partial.
484
 *
485
 * @return string
486
 *   A month.
487
 */
488
function partialToMonth($partial) {
489
  if (is_string($partial)) {
490
    $month = drupal_substr($partial, 5, 2);
491
    if (preg_match("/[0-9][0-9]/", $month)) {
492
      return $month;
493
    }
494
  }
495
  return '';
496
}
497

    
498
/**
499
 * Converts an ISO 8601 org.joda.time.Partial to a day.
500
 *
501
 * This function expects an ISO 8601 time representation of a
502
 * org.joda.time.Partial of the form yyyy-MM-dd and returns the day as string.
503
 * In case the day is unknown (= ???) NULL is returned.
504
 *
505
 * @param string $partial
506
 *   ISO 8601 time representation of a org.joda.time.Partial.
507
 *
508
 * @return string
509
 *   A day.
510
 */
511
function partialToDay($partial) {
512
  if (is_string($partial)) {
513
    $day = drupal_substr($partial, 8, 2);
514
    if (preg_match("/[0-9][0-9]/", $day)) {
515
      return $day;
516
    }
517
  }
518
  return '';
519
}
520

    
521
/**
522
 * Converts an ISO 8601 org.joda.time.Partial to YYYY-MM-DD.
523
 *
524
 * This function expects an ISO 8601 time representations of a
525
 * org.joda.time.Partial of the form yyyy-MM-dd and returns
526
 * four digit year, month and day with dashes:
527
 * YYYY-MM-DD eg: "2012-06-30", "1956-00-00"
528
 *
529
 * The partial may contain question marks eg: "1973-??-??",
530
 * these are turned in to '00' or are stripped depending of the $stripZeros
531
 * parameter.
532
 *
533
 * @param string $partial
534
 *   org.joda.time.Partial.
535
 * @param bool $stripZeros
536
 *   If set to TRUE the zero (00) month and days will be hidden:
537
 *   eg 1956-00-00 becomes 1956. The default is TRUE.
538
 * @param string @format
539
 * 	 Can ve used to specify the format of the date string, currently the following format strings are supported
540
 *    - "YYYY": Year only
541
 *    - "YYYY-MM-DD": this is the default
542
 *
543
 * @return string
544
 *   YYYY-MM-DD formatted year, month, day.
545
 */
546
function partialToDate($partial, $stripZeros = TRUE, $format= "YYYY-MM-DD") {
547

    
548
  $y = NULL; $m = NULL; $d = NULL;
549

    
550
  if(strpos($format, 'YY') !== FALSE){
551
    $y = partialToYear($partial);
552
  }
553
  if(strpos($format, 'MM') !== FALSE){
554
    $m = partialToMonth($partial);
555
  }
556
  if(strpos($format, 'DD') !== FALSE){
557
    $d = partialToDay($partial);
558
  }
559

    
560
  $y = $y ? $y : '0000';
561
  $m = $m ? $m : '00';
562
  $d = $d ? $d : '00';
563

    
564
  $date = '';
565

    
566
  if ($y == '0000' && $stripZeros && $m == '00' && $d == '00') {
567
    return '';
568
  }
569
  else {
570
    $date = $y;
571
  }
572

    
573
  if ($m == '00' && $stripZeros && $d == '00') {
574
    return $date;
575
  }
576
  else {
577
    $date .= "-" . $m;
578
  }
579

    
580
  if ($d == '00' && $stripZeros) {
581
    return $date;
582
  }
583
  else {
584
    $date .= "-" . $d;
585
  }
586
  return $date;
587
}
588

    
589
/**
590
 * Converts a time period to a string.
591
 *
592
 * See also partialToDate($partial, $stripZeros).
593
 *
594
 * @param object $period
595
 *   An JodaTime org.joda.time.Period object.
596
 * @param bool $stripZeros
597
 *   If set to True, the zero (00) month and days will be hidden:
598
 *   eg 1956-00-00 becomes 1956. The default is TRUE.
599
 * @param string @format
600
 * 	 Can ve used to specify the format of the date string, currently the following format strings are supported
601
 *    - "YYYY": Year only
602
 *    - "YYYY-MM-DD": this is the default
603
 *
604
 * @return string
605
 *   Returns a date in the form of a string.
606
 */
607
function timePeriodToString($period, $stripZeros = TRUE, $format = "YYYY-MM-DD") {
608
  $dateString = '';
609
  if($period->freeText){
610
    $dateString = $period->freeText;
611
  } else {
612
    if ($period->start) {
613
      $dateString = partialToDate($period->start, $stripZeros, $format);
614
    }
615
    if ($period->end) {
616
      $end_str = partialToDate($period->end, $stripZeros, $format);
617
      $dateString .= ($dateString && $end_str ? ' ' . t('to') . ' ' : '') . $end_str;
618
    }
619
  }
620
  return $dateString;
621
}
622

    
623
/**
624
 * returns the earliest date available in the $period in a normalized
625
 * form suitable for sorting, e.g.:
626
 *
627
 *  - 1956-00-00
628
 *  - 0000-00-00
629
 *  - 1957-03-00
630
 *
631
 * that is either the start date is returned if set otherwise the
632
 * end date
633
 *
634
 * @param  $period
635
 *    An JodaTime org.joda.time.Period object.
636
 * @return string normalized form of the date
637
 *   suitable for sorting
638
 */
639
function timePeriodAsOrderKey($period) {
640
  $dateString = '';
641
  if ($period->start) {
642
    $dateString = partialToDate($period->start, false);
643
  }
644
  if ($period->end) {
645
    $dateString .= partialToDate($period->end, false);
646
  }
647
  return $dateString;
648
}
649

    
650
/**
651
 * Composes a absolute CDM web service URI with parameters and querystring.
652
 *
653
 * @param string $uri_pattern
654
 *   String with place holders ($0, $1, ..) that should be replaced by the
655
 *   according element of the $pathParameters array.
656
 * @param array $pathParameters
657
 *   An array of path elements, or a single element.
658
 * @param string $query
659
 *   A query string to append to the URL.
660
 *
661
 * @return string
662
 *   A complete URL with parameters to a CDM webservice.
663
 */
664
function cdm_compose_ws_url($uri_pattern, $pathParameters = array(), $query = NULL) {
665
  if (empty($pathParameters)) {
666
    $pathParameters = array();
667
  }
668
  $path = cdm_compose_url($uri_pattern, $pathParameters, $query);
669
  $url = cdm_webservice_url() . $path;
670
  return $url;
671
}
672

    
673
/**
674
 * Wrapper to access the 'cdm_webservice_url' and to ensure that
675
 * the returned webservice url scheme conforms to the scheme of the
676
 * page request.
677
 *
678
 * @param string $default optional parameter to set the default, by default an empty string is returned
679
 */
680
function cdm_webservice_url($default = ''){
681
  static $conform_ws_url = null;
682
  if($conform_ws_url === null) {
683
    $ws_url = variable_get('cdm_webservice_url', $default);
684
    if($ws_url && $_SERVER['REQUEST_SCHEME'] == 'https' && parse_url($ws_url, PHP_URL_SCHEME) === 'http'){
685
      $conform_ws_url = preg_replace("/^http\:/", "https:", $ws_url);
686
      drupal_set_message('The <stong>CDM web service URL</stong> scheme should be set to <strong>HTTPS</strong> the '
687
        . l('CDM Data Portal settings', 'admin/config/cdm_dataportal/settings'), 'warning');
688
    } else {
689
      $conform_ws_url = $ws_url;
690
    }
691
  }
692
  return $conform_ws_url;
693
}
694

    
695
/**
696
 * Composes a relative Url with parameters and querystring.
697
 *
698
 * @param string $uri_pattern
699
 *   String with place holders ($0, $1, ..) that should be replaced by the
700
 *   according element of the $pathParameters array.
701
 * @param array $pathParameters
702
 *   An array of path elements, or a single element.
703
 * @param string $query
704
 *   A query string to append to the URL.
705
 *
706
 * @return string
707
 *   A relative URL with query parameters.
708
 */
709
function cdm_compose_url($uri_pattern, $pathParameters, $query = NULL) {
710

    
711
  // (1)
712
  // Substitute all place holders ($0, $1, ..) in the $uri_pattern by the
713
  // according element of the $pathParameters array.
714
  static $helperArray = [];
715
  if (isset($pathParameters) && !is_array($pathParameters)) {
716
    $helperArray[0] = $pathParameters;
717
    $pathParameters = $helperArray;
718
  }
719

    
720
  $i = 0;
721
  while (strpos($uri_pattern, "$" . $i) !== FALSE) {
722
    if (count($pathParameters) <= $i) {
723
      drupal_set_message('cdm_compose_url(): missing pathParameter ' . $i .' for ' . $uri_pattern, 'error');
724
      break;
725
    }
726
    $uri_pattern = str_replace("$" . $i, rawurlencode($pathParameters[$i]), $uri_pattern);
727
    ++$i;
728
  }
729

    
730
  // (2)
731
  // Append all remaining element of the $pathParameters array as path
732
  // elements.
733
  if (count($pathParameters) > $i) {
734
    // Strip trailing slashes.
735
    if (strrchr($uri_pattern, '/') == strlen($uri_pattern)) {
736
      $uri_pattern = substr($uri_pattern, 0, strlen($uri_pattern) - 1);
737
    }
738
    while (count($pathParameters) > $i) {
739
      $uri_pattern .= '/' . rawurlencode($pathParameters[$i]);
740
      ++$i;
741
    }
742
  }
743

    
744
  // (3)
745
  // Append the query string supplied by $query.
746
  $uri_pattern = append_query_parameters($uri_pattern, $query);
747

    
748
  $path = $uri_pattern;
749
  return $path;
750
}
751

    
752
/**
753
 * @param string $uri
754
 *
755
 * @param $query_string
756
 *
757
 * @return string
758
 */
759
function append_query_parameters($uri, $query_string) {
760

    
761
  if (isset($query_string)) {
762
    $uri .= (strpos($uri, '?') !== FALSE ? '&' : '?') . $query_string;
763
  }
764

    
765
  return $uri;
766
}
767

    
768
/**
769
 * @todo wouldn't it more elegant and secure to only pass a uuid and additional function parameters
770
 *     together with a theme name to such a proxy function?
771
 *     Well this would not be covering all use cases but maybe all which involve AHAH.
772
 *     Maybe we want to have two different proxy functions, one with theming and one without?
773
 *
774
 * @param string $uri
775
 *     A URI to a CDM Rest service from which to retrieve an object
776
 * @param string|null $hook
777
 *     (optional) The hook name to which the retrieved object should be passed.
778
 *     Hooks can either be a theme_hook() or compose_hook() implementation
779
 *     'theme' hook functions return a string whereas 'compose' hooks are returning render arrays
780
 *     suitable for drupal_render()
781
 *
782
 * @todo Please document this function.
783
 * @see http://drupal.org/node/1354
784
 */
785
function proxy_content($uri, $hook = NULL) {
786

    
787
  $args = func_get_args();
788
  $do_gzip = function_exists('gzencode');
789
  $uriEncoded = array_shift($args);
790
  $uri = urldecode($uriEncoded);
791
  $hook = array_shift($args);
792
  $request_method = strtoupper($_SERVER["REQUEST_METHOD"]);
793

    
794
  $post_data = null;
795

    
796
  if ($request_method == "POST" || $request_method == "PUT") {
797
    // read response body via inputstream module
798
    $post_data = file_get_contents("php://input");
799
  }
800

    
801
  // Find and deserialize arrays.
802
  foreach ($args as &$arg) {
803
    // FIXME use regex to find serialized arrays.
804
    //       or should we accept json instead of php serializations?
805
    if (strpos($arg, "a:") === 0) {
806
      $arg = unserialize($arg);
807
    }
808
  }
809

    
810
  // In all these cases perform a simple get request.
811
  // TODO reconsider caching logic in this function.
812

    
813
  if (empty($hook)) {
814
    // simply return the webservice response
815
    // Print out JSON, the cache cannot be used since it contains objects.
816
    $http_response = cdm_http_request($uri, $request_method, $post_data);
817
    if (isset($http_response->headers)) {
818
      foreach ($http_response->headers as $hname => $hvalue) {
819
        drupal_add_http_header($hname, $hvalue);
820
      }
821
    }
822
    if (isset($http_response->data)) {
823
      print $http_response->data;
824
      flush();
825
    }
826
    exit(); // leave drupal here
827
  } else {
828
    // $hook has been supplied
829
    // handle $hook either as compose or theme hook
830
    // pass through theme or compose hook
831
    // compose hooks can be called without data, therefore
832
    // passing the $uri in this case is not always a requirement
833

    
834
    if($uri && $uri != 'NULL') {
835
    // do a security check since the $uri will be passed
836
    // as absolute URI to cdm_ws_get()
837
      if (!_is_cdm_ws_uri($uri)) {
838
        drupal_set_message(
839
          'Invalid call of proxy_content() with callback parameter \'' . $hook . '\' and URI:' . $uri,
840
          'error'
841
        );
842
        return '';
843
      }
844

    
845
      $obj = cdm_ws_get($uri, NULL, $post_data, $request_method, TRUE);
846
    } else {
847
      $obj = NULL;
848
    }
849

    
850
    $response_data = NULL;
851

    
852
    if (function_exists('compose_' . $hook)){
853
      // call compose hook
854

    
855
      $elements =  call_user_func('compose_' . $hook, $obj);
856
      // pass the render array to drupal_render()
857
      $response_data = drupal_render($elements);
858
    } else {
859
      // call theme hook
860

    
861
      // TODO use theme registry to get the registered hook info and
862
      //    use these defaults
863
      switch($hook) {
864
        case 'cdm_taxontree':
865
          $variables = array(
866
            'tree' => $obj,
867
            'filterIncludes' => isset($args[0]) ? $args[0] : NULL,
868
            'show_filter_switch' => isset($args[1]) ? $args[1] : FALSE,
869
            'tree_node_callback' => isset($args[2]) ? $args[2] : FALSE,
870
            'element_name'=> isset($args[3]) ? $args[3] : FALSE,
871
            );
872
          $response_data = theme($hook, $variables);
873
          break;
874

    
875
        case 'cdm_list_of_taxa':
876
            $variables = array(
877
              'records' => $obj,
878
              'freetextSearchResults' => isset($args[0]) ? $args[0] : array(),
879
              'show_classification' => isset($args[1]) ? $args[1] : FALSE);
880
            $response_data = theme($hook, $variables);
881
            break;
882

    
883
        default:
884
          drupal_set_message(t(
885
          'Theme !theme is not yet supported by the function !function.', array(
886
          '!theme' => $hook,
887
          '!function' => __FUNCTION__,
888
          )), 'error');
889
          break;
890
      } // END of theme hook switch
891
    } // END of tread as theme hook
892

    
893

    
894
    if($do_gzip){
895
      $response_data = gzencode($response_data, 2, FORCE_GZIP);
896
      drupal_add_http_header('Content-Encoding', 'gzip');
897
    }
898
    drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
899
    drupal_add_http_header('Content-Length', strlen($response_data));
900

    
901
    print $response_data;
902
  } // END of handle $hook either as compose ot theme hook
903

    
904
}
905

    
906
/**
907
 * @todo Please document this function.
908
 * @see http://drupal.org/node/1354
909
 */
910
function setvalue_session() {
911
  if ($_REQUEST['var'] && strlen($_REQUEST['var']) > 4) {
912
    $var_keys = substr($_REQUEST['var'], 1, strlen($_REQUEST['var']) - 2);
913
    $var_keys = explode('][', $var_keys);
914
  }
915
  else {
916
    return;
917
  }
918
  $val = isset($_REQUEST['val']) ? $_REQUEST['val'] : NULL;
919

    
920
  // Prevent from malicous tags.
921
  $val = strip_tags($val);
922

    
923
  $session_var = &$_SESSION;
924
  //$i = 0;
925
  foreach ($var_keys as $key) {
926
    // $hasMoreKeys = ++$i < count($session);
927
    if (!isset($session_var[$key]) || !is_array($session_var[$key])) {
928
      $session_var[$key] = array();
929
    }
930
    $session_var = &$session_var[$key];
931
  }
932
  $session_var = $val;
933
  if (isset($_REQUEST['destination'])) {
934
    drupal_goto($_REQUEST['destination']);
935
  }
936
}
937

    
938
/**
939
 * @todo Please document this function.
940
 * @see http://drupal.org/node/1354
941
 */
942
function uri_uriByProxy($uri, $theme = FALSE) {
943
  // usage: url('cdm_api/proxy/'.urlencode($content_url)."/$theme");)
944
  return url('cdm_api/proxy/' . urlencode($uri) . (isset($theme) ? "/$theme" : ''));
945
}
946

    
947
/**
948
 * Composes the the absolute REST service URI to the annotations pager
949
 * for the given CDM entity.
950
 *
951
 * NOTE: Not all CDM Base types are yet supported.
952
 *
953
 * @param $cdm_entity
954
 *   The CDM entity to construct the annotations pager uri for
955
 */
956
function cdm_compose_annotations_uri($cdm_entity) {
957

    
958
  $cdm_entity_type = $cdm_entity->class;
959

    
960
  if($cdm_entity_type == 'DerivedUnitFacade'){
961
    // TODO with the below code we may miss annotations.
962
    //  Better implement a derivedUnitFacade/{uuid}/annotations service and use that instead
963
    if(isset($cdm_entity->fieldUnitEntityReference)){
964
      $cdm_entity_type = $cdm_entity->fieldUnitEntityReference->type;
965
      $cdm_entity_uuid = $cdm_entity->fieldUnitEntityReference->uuid;
966
    } elseif(isset($cdm_entity->baseUnitEntityReference)){
967
      $cdm_entity_type = $cdm_entity->baseUnitEntityReference->type;
968
      $cdm_entity_uuid = $cdm_entity->baseUnitEntityReference->uuid;
969
    }
970
  } elseif ($cdm_entity_type == 'TypedEntityReference'){
971
    $cdm_entity_type = $cdm_entity->type;
972
    $cdm_entity_uuid = $cdm_entity->uuid;
973
  } else {
974
    if (isset($cdm_entity->uuid)) {
975
      $cdm_entity_uuid = $cdm_entity->uuid;
976
    }
977
  }
978

    
979
  if(!isset($cdm_entity_uuid) || !isset($cdm_entity_type) || !$cdm_entity_type ){
980
    // silently give up
981
    return;
982
  }
983

    
984
  $ws_base_uri = cdm_ws_base_uri($cdm_entity_type);
985

    
986
  if($ws_base_uri === null){
987
      trigger_error(check_plain('Unsupported CDM Class - no annotations available for ' . $cdm_entity_type), E_USER_ERROR);
988
  }
989
  return cdm_compose_ws_url($ws_base_uri, array(
990
    $cdm_entity_uuid,
991
    'annotations',
992
  ));
993
}
994

    
995
/**
996
 * Provides the base URI of the cdm REST service responsible for the passed simple name
997
 * of a CDM java class. For example 'TaxonName' is the simple name of 'eu.etaxonomy.cdm.model.name.TaxonName'
998
 *
999
 * @param $cdm_type_simple
1000
 *    simple name of a CDM java class
1001
 * @return null|string
1002
 */
1003
function cdm_ws_base_uri($cdm_type_simple)
1004
{
1005
  switch ($cdm_type_simple) {
1006
    case 'TaxonNode':
1007
    case 'TaxonNodeDto':
1008
      $ws_base_uri = CDM_WS_TAXONNODE;
1009
      break;
1010

    
1011
    case 'TaxonBase':
1012
    case 'Taxon':
1013
    case 'Synonym':
1014
      $ws_base_uri = CDM_WS_TAXON;
1015
      break;
1016

    
1017
    case 'TaxonName':
1018
      $ws_base_uri = CDM_WS_NAME;
1019
      break;
1020

    
1021
    case 'Media':
1022
      $ws_base_uri = CDM_WS_MEDIA;
1023
      break;
1024

    
1025
    case 'Reference':
1026
      $ws_base_uri = CDM_WS_REFERENCE;
1027
      break;
1028

    
1029
    case 'Registration':
1030
      $ws_base_uri = CDM_WS_REGISTRATION;
1031
      break;
1032

    
1033
    case 'FieldUnit':
1034
    case 'DerivedUnit':
1035
    case 'DnaSample':
1036
    case 'MediaSpecimen':
1037
    case 'DerivedUnitDTO':
1038
    case 'FieldUnitDTO':
1039
    case 'DNASampleDTO':
1040
      $ws_base_uri = CDM_WS_OCCURRENCE;
1041
      break;
1042

    
1043
    case 'Amplification':
1044
    case 'DerivationEvent':
1045
    case 'DeterminationEvent':
1046
    case 'GatheringEvent':
1047
    case 'MaterialOrMethodEvent':
1048
    case 'SingleRead':
1049
      $ws_base_uri = CDM_WS_EVENTBASE;
1050
      break;
1051

    
1052
    case 'Distribution':
1053
    case 'TextData':
1054
    case 'TaxonInteraction':
1055
    case 'QuantitativeData':
1056
    case 'IndividualsAssociation':
1057
    case 'CommonTaxonName':
1058
    case 'CategoricalData':
1059
      $ws_base_uri = CDM_WS_DESCRIPTIONELEMENT;
1060
      break;
1061

    
1062
    case 'Person':
1063
    case 'Team':
1064
    case 'AgentBase':
1065
      $ws_base_uri = CDM_WS_AGENT;
1066
      break;
1067

    
1068
    case 'PolytomousKey':
1069
    case 'MediaKey':
1070
    case 'MultiAccessKey':
1071
      $ws_base_uri = $cdm_type_simple;
1072
      $ws_base_uri[0] = strtolower($ws_base_uri[0]);
1073
      break;
1074

    
1075
    case 'TextualTypeDesignation':
1076
    case 'SpecimenTypeDesignation':
1077
    case 'NameTypeDesignation':
1078
    case 'TypeDesignationDTO':
1079
      $ws_base_uri = CDM_WS_TYPEDESIGNATION;
1080
      break;
1081
    default:
1082
      $ws_base_uri = null;
1083
      drupal_set_message(
1084
        t('cdm_ws_base_uri()  - cdm type name "@cdm_type_simple" unsupported',
1085
          array('@cdm_type_simple' => $cdm_type_simple )),
1086
        'error');
1087
  }
1088
  return $ws_base_uri;
1089
}
1090

    
1091
function cdm_type_has_tagged_text($cdm_type_name){
1092
  return array_search($cdm_type_name, ['TaxonName', 'Taxon', 'Synonym']) !== false;
1093
}
1094

    
1095
/**
1096
 * Enter description here...
1097
 *
1098
 * @param string $resource_uri
1099
 * @param int $page_size
1100
 *   The maximum number of entities returned per page.
1101
 *   The default page size as configured in the cdm server
1102
 *   will be used if set to NULL
1103
 *   to return all entities in a single page).
1104
 * @param int $page_index
1105
 *   The number of the page to be returned, the first page has the
1106
 *   page_index = 0
1107
 * @param array $query
1108
 *   A array holding the HTTP request query parameters for the request
1109
 * @param string $method
1110
 *   The HTTP method to use, valid values are "GET" or "POST"
1111
 * @param bool $absolute_uri
1112
 *   TRUE when the URL should be treated as absolute URL.
1113
 *
1114
 * @return object
1115
 *   A CDM Pager object
1116
 *
1117
 */
1118
function cdm_ws_page($resource_uri, $page_size, $page_index, array $query = array(), $method = 'GET', $absolute_uri = FALSE) {
1119

    
1120
  $query['pageIndex'] = $page_index;
1121
  $query['pageSize'] = $page_size;
1122

    
1123
  $pager = cdm_ws_get($resource_uri, NULL, queryString($query), $method, $absolute_uri);
1124
  if(is_array($pager)){
1125
    trigger_error("Expecting web service to return pager objects but received an array:<br/>" . $resource_uri . '?' . queryString($query) . '<br/>Wrapping response in pager to recover from error.', E_USER_WARNING);
1126
    $records = $pager;
1127
    $pager = new stdClass();
1128
    $pager->records = $records;
1129
    $pager->count = count($records);
1130
    $pager->pageSize = $pager->count;
1131
    $pager->nextIndex = null;
1132
  }
1133
  return $pager;
1134
}
1135

    
1136

    
1137
/**
1138
 * Sends a http GET request to the generic page method which allows for filtering entities by Restrictions.
1139
 *
1140
 * @param $cdm_entity_type
1141
 * @param $class_restriction
1142
 *   Optional param to narrow down polymorph types to a specific type.
1143
 * @param array $restrictions
1144
 *   An array of Restriction objects
1145
 * @param array $init_strategy
1146
 *   The init strategy to initialize the entity beans while being loaded from the
1147
 *   persistent storage by the cdm
1148
 * @param int $page_size
1149
 *   The maximum number of entities returned per page.
1150
 *   The default page size as configured in the cdm server
1151
 *   will be used if set to NULL
1152
 *   to return all entities in a single page).
1153
 * @param int $page_index
1154
 *   The number of the page to be returned, the first page has the
1155
 *   pageNumber = 0
1156
 *
1157
 * @return object
1158
 *   A CDM Pager object
1159
 */
1160
function cdm_ws_page_by_restriction($cdm_entity_type, $class_restriction, array $restrictions, array $init_strategy, $page_size, $page_index) {
1161

    
1162
  $restrictions_json = array(); // json_encode($restrictions);
1163
  foreach ($restrictions as $restr){
1164
    $restrictions_json[] = json_encode($restr);
1165
  }
1166
  $filter_parameters = [
1167
    'restriction' => $restrictions_json,
1168
    'initStrategy' => $init_strategy
1169
  ];
1170
  if($class_restriction){
1171
    $filter_parameters['class'] = $class_restriction;
1172
  }
1173

    
1174
  return cdm_ws_page(
1175
      'portal/' . cdm_ws_base_uri($cdm_entity_type),
1176
      $page_size,
1177
      $page_index,
1178
    $filter_parameters,
1179
    "GET"
1180
    );
1181
}
1182

    
1183
/**
1184
 * Fetches all entities returned by the the generic page method for the Restrictions applied as filter.
1185
 *
1186
 * @param $cdm_entity_type
1187
 * @param $class_restriction
1188
 *   Optional param to narrow down polymorph types to a specific type.
1189
 * @param array $restrictions
1190
 *   An array of Restriction objects
1191
 * @param array $init_strategy
1192
 *   The init strategy to initialize the entity beans while being loaded from the
1193
 *   persistent storage by the cdm
1194
 *
1195
 * @return array
1196
 *   A array of CDM entities
1197
 */
1198
function cdm_ws_fetch_all_by_restriction($cdm_entity_type, $class_restriction, array $restrictions, array $init_strategy){
1199
  $page_index = 0;
1200
  // using a bigger page size to avoid to many multiple requests
1201
  $page_size = 500;
1202
  $entities = array();
1203

    
1204
  while ($page_index !== FALSE && $page_index < 1){
1205
    $pager =  cdm_ws_page_by_restriction($cdm_entity_type, $class_restriction, $restrictions, $init_strategy, $page_size, $page_index);
1206
    if(isset($pager->records) && is_array($pager->records)) {
1207
      $entities = array_merge($entities, $pager->records);
1208
      if(!empty($pager->nextIndex)){
1209
        $page_index = $pager->nextIndex;
1210
      } else {
1211
        $page_index = FALSE;
1212
      }
1213
    } else {
1214
      $page_index = FALSE;
1215
    }
1216
  }
1217
  return $entities;
1218
}
1219

    
1220

    
1221
  /**
1222
 * Fetches all entities from the given REST endpoint using the pager mechanism.
1223
 *
1224
 * @param string $resourceURI
1225
 * @param array $query
1226
 *   A array holding the HTTP request query parameters for the request
1227
 * @param string $method
1228
 *   The HTTP method to use, valid values are "GET" or "POST";
1229
 * @param bool $absoluteURI
1230
 *   TRUE when the URL should be treated as absolute URL.
1231
 *
1232
 * @return array
1233
 *     A list of CDM entitites
1234
 *
1235
 */
1236
function cdm_ws_fetch_all($resourceURI, array $query = array(), $method = 'GET', $absoluteURI = FALSE) {
1237
  $page_index = 0;
1238
  // using a bigger page size to avoid too many multiple requests
1239
  $page_size = 500;
1240
  $entities = [];
1241

    
1242
  while (true){
1243
    $pager =  cdm_ws_page($resourceURI, $page_size, $page_index, $query,  $method, $absoluteURI);
1244
    if(isset($pager->records) && is_array($pager->records)) {
1245
      $entities = array_merge($entities, $pager->records);
1246
      if(is_numeric($pager->nextIndex) && $page_index < $pager->nextIndex){
1247
        $page_index = $pager->nextIndex;
1248
      } else {
1249
        break;
1250
      }
1251
    } else {
1252
      break;
1253
    }
1254
  }
1255
  return $entities;
1256
}
1257

    
1258
/*
1259
function cdm_ws_taxonomy_compose_resourcePath($path = NULL){
1260
  $viewrank = _cdm_taxonomy_compose_viewrank();
1261
  return CDM_WS_PORTAL_TAXONOMY . '/' . ($viewrank ? $viewrank : '' ) . ($path
1262
  ? '/' . $path : '') ;
1263
}
1264
*/
1265

    
1266
/**
1267
 * @todo Enter description here...
1268
 *
1269
 * @param string $taxon_uuid
1270
 *  The UUID of a cdm taxon instance
1271
 * @param string $ignore_rank_limit
1272
 *   Whether to ignore the variable 'taxontree_ranklimit' set by admin in the settings
1273
 *
1274
 * @return string
1275
 *   A cdm REST service URL path to a Classification
1276
 */
1277
function cdm_compose_taxonomy_root_level_path($taxon_uuid = FALSE, $ignore_rank_limit = FALSE) {
1278

    
1279
  $view_uuid = get_current_classification_uuid();
1280
  $rank_uuid = NULL;
1281
  if (!$ignore_rank_limit) {
1282
    $rank_uuid = variable_get(TAXONTREE_RANKLIMIT, TAXONTREE_RANKLIMIT_DEFAULT);
1283
  }
1284

    
1285
  if (!empty($taxon_uuid)) {
1286
    return cdm_compose_ws_url(CDM_WS_PORTAL_TAXONOMY_CHILDNODES_OF_TAXON, array(
1287
      $view_uuid,
1288
      $taxon_uuid,
1289
    ));
1290
  }
1291
  else {
1292
    if (is_uuid($rank_uuid)) {
1293
      return cdm_compose_ws_url(CDM_WS_PORTAL_TAXONOMY_CHILDNODES_AT_RANK, array(
1294
        $view_uuid,
1295
        $rank_uuid,
1296
      ));
1297
    }
1298
    else {
1299
      return cdm_compose_ws_url(CDM_WS_PORTAL_TAXONOMY_CHILDNODES, array(
1300
        $view_uuid,
1301
      ));
1302
    }
1303
  }
1304
}
1305

    
1306
/**
1307
 * Retrieves from the cdm web service with the first level of childnodes of a classification.
1308
 *
1309
 * The level is either the real root level ot it is a lover level if a rank limit has been set.
1310
 * (@see  cdm_compose_taxonomy_root_level_path() for more on the rank limit).
1311
 *
1312
 * Operates in two modes depending on whether the parameter
1313
 * $taxon_uuid is set or NULL.
1314
 *
1315
 * A) $taxon_uuid = NULL:
1316
 *  1. retrieve the Classification for the uuid set in the $_SESSION['cdm']['taxonomictree_uuid']
1317
 *  2. otherwise return the default classification as defined by the admin via the settings
1318
 *
1319
 * b) $taxon_uuid is set:
1320
 *   return the classification to whcih the taxon belongs to.
1321
 *
1322
 * @param UUID $taxon_uuid
1323
 *   The UUID of a cdm taxon instance
1324
 */
1325
function cdm_ws_taxonomy_root_level($taxon_uuid = NULL) {
1326

    
1327
    $response = NULL;
1328

    
1329
    // 1st try
1330
    $response = cdm_ws_get(cdm_compose_taxonomy_root_level_path($taxon_uuid), NULL, NULL, 'GET', TRUE);
1331

    
1332
    if ($response == NULL) {
1333
      // 2dn try by ignoring the rank limit
1334
      $response = cdm_ws_get(cdm_compose_taxonomy_root_level_path($taxon_uuid, TRUE), NULL, NULL, 'GET', TRUE);
1335
    }
1336

    
1337
    if ($response == NULL) {
1338
      // 3rd try, last fallback:
1339
      //    return the default classification
1340
      if (isset($_SESSION['cdm']['taxonomictree_uuid']) && is_uuid($_SESSION['cdm']['taxonomictree_uuid'])) {
1341
        // Delete the session value and try again with the default.
1342
        unset($_SESSION['cdm']['taxonomictree_uuid']);
1343
        drupal_set_message("Could not find a valid classification, falling back to the default classification.", 'warning');
1344
        return cdm_ws_taxonomy_root_level($taxon_uuid);
1345
      }
1346
      else {
1347
        // Check if taxonomictree_uuid is valid.
1348
        // expecting an array of taxonNodes,
1349
        // empty classifications are ok so no warning in this case!
1350
        $test = cdm_ws_get(cdm_compose_taxonomy_root_level_path(), NULL, NULL, 'GET', TRUE);
1351
        if (!is_array($test)) {
1352
          // The default set by the admin seems to be invalid or is not even set.
1353
          drupal_set_message(_no_classfication_uuid_message(), 'warning');
1354
        } else if (count($test) == 0) {
1355
          // The default set by the admin seems to be invalid or is not even set.
1356
          drupal_set_message("The chosen classification is empty.", 'status');
1357
        }
1358
      }
1359
    }
1360

    
1361
  return $response;
1362
}
1363

    
1364
/**
1365
 * Determines the tree path of the taxon given as uuid to the root of the classification tree.
1366
 * 
1367
 * The root either is the absolute root of the tree or a rank specific root if the TAXONTREE_RANKLIMIT
1368
 * variable is set.
1369
 *
1370
 * @param string $taxon_uuid
1371
 *
1372
 * @return array
1373
 *   An array of CDM TaxonNodeDTO objects
1374
 */
1375
function cdm_ws_taxonomy_pathFromRoot($taxon_uuid) {
1376
  $view_uuid = get_current_classification_uuid();
1377
  $rank_uuid = variable_get(TAXONTREE_RANKLIMIT, TAXONTREE_RANKLIMIT_DEFAULT);
1378

    
1379
  $response = NULL;
1380
  if (is_uuid($rank_uuid)) {
1381
    $response = cdm_ws_get(CDM_WS_PORTAL_TAXONOMY_PATH_FROM_TO_RANK, array(
1382
      $view_uuid,
1383
      $taxon_uuid,
1384
      $rank_uuid,
1385
    ));
1386
  }
1387
  else {
1388
    $response = cdm_ws_get(CDM_WS_PORTAL_TAXONOMY_PATH_FROM, array(
1389
      $view_uuid,
1390
      $taxon_uuid,
1391
    ));
1392
  }
1393

    
1394
  if ($response == NULL) {
1395
    // Error handing.
1396
//    if (is_uuid($_SESSION['cdm']['taxonomictree_uuid'])) {
1397
//      // Delete the session value and try again with the default.
1398
//      unset($_SESSION['cdm']['taxonomictree_uuid']);
1399
//      return cdm_ws_taxonomy_pathFromRoot($taxon_uuid);
1400
//    }
1401
//    else {
1402
      // Check if taxonomictree_uuid is valid.
1403
      $test = cdm_ws_get(cdm_compose_taxonomy_root_level_path(), NULL, NULL, 'GET', TRUE);
1404
      if ($test == NULL) {
1405
        // The default set by the admin seems to be invalid or is not even set.
1406
        drupal_set_message(_no_classfication_uuid_message(), 'warning');
1407
      }
1408
//    }
1409
  }
1410

    
1411
  return $response;
1412
}
1413

    
1414

    
1415
// =============================Terms and Vocabularies ========================================= //
1416

    
1417
/**
1418
 * Returns the localized representation for the given term.
1419
 *
1420
 * @param Object $definedTermBase
1421
 * 	  of cdm type DefinedTermBase
1422
 * @return string
1423
 * 	  the localized representation_L10n of the term,
1424
 *    otherwise the titleCache as fall back,
1425
 *    otherwise the default_representation which defaults to an empty string
1426
 */
1427
function cdm_term_representation($definedTermBase, $default_representation = '') {
1428
  if ( isset($definedTermBase->representation_L10n) ) {
1429
    return $definedTermBase->representation_L10n;
1430
  } elseif ( isset($definedTermBase->titleCache)) {
1431
    return $definedTermBase->titleCache;
1432
  }
1433
  return $default_representation;
1434
}
1435

    
1436
/**
1437
 * Returns the abbreviated localized representation for the given term.
1438
 *
1439
 * @param Object $definedTermBase
1440
 * 	  of cdm type DefinedTermBase
1441
 * @return string
1442
 * 	  the localized representation_L10n_abbreviatedLabel of the term,
1443
 *    if this representation is not available the function delegates the
1444
 *    call to cdm_term_representation()
1445
 */
1446
function cdm_term_representation_abbreviated($definedTermBase, $default_representation = '') {
1447
  if ( isset($definedTermBase->representation_L10n_abbreviatedLabel) ) {
1448
    return $definedTermBase->representation_L10n_abbreviatedLabel;
1449
  } else {
1450
    cdm_term_representation($definedTermBase, $default_representation);
1451
  }
1452
}
1453

    
1454
/**
1455
 * Transforms the list of the given term base instances to a alphabetical ordered options array.
1456
 *
1457
 * The options array is suitable for drupal form API elements that allow multiple choices.
1458
 * @see http://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7#options
1459
 *
1460
 * @param array $terms
1461
 *   a list of CDM DefinedTermBase instances
1462
 *
1463
 * @param $term_label_callback
1464
 *   A callback function to override the term representations
1465
 *
1466
 * @param bool $empty_option
1467
 *   An additional element do be placed at the beginning og the list. This element will be the default option.
1468
 *
1469
 * @return array
1470
 *   the terms in an array (key: uuid => value: label) as options for a form element that allows multiple choices.
1471

    
1472
 */
1473
function cdm_terms_as_options($terms, $term_label_callback = NULL, $empty_option = FALSE){
1474
  $options = array();
1475
  if(isset($terms) && is_array($terms)) {
1476
    foreach ($terms as $term) {
1477
      if ($term_label_callback && function_exists($term_label_callback)) {
1478
        $options[$term->uuid] = call_user_func($term_label_callback, $term);
1479
      } else {
1480
        //TODO use cdm_term_representation() here?
1481
        $options[$term->uuid] = t('@term', array('@term' => $term->representation_L10n));
1482
      }
1483
    }
1484
  }
1485

    
1486
  if($empty_option !== FALSE){
1487
    array_unshift ($options, "");
1488
  }
1489

    
1490
  return $options;
1491
}
1492

    
1493
/**
1494
 * Creates and array of options for drupal select form elements.
1495
 *
1496
 * @param $vocabulary_uuid
1497
 *   The UUID of the CDM Term Vocabulary
1498
 * @param $term_label_callback
1499
 *   An optional call back function which can be used to modify the term label
1500
 * @param bool $empty_option
1501
 *   An additional element do be placed at the beginning og the list. This element will be the default option.
1502
 * @param array $include_filter
1503
 *   An associative array consisting of a field name an regular expression. All term matching
1504
 *   these filter are included. The value of the field is converted to a String by var_export()
1505
 *   so a boolean 'true' can be matched by '/true/'
1506
 * @param string $order_by
1507
 *   One of the order by constants defined in this file
1508
 * @return array
1509
 *   the terms in an array (key: uuid => value: label) as options for a form element that allows multiple choices.
1510
 */
1511
function cdm_vocabulary_as_option($vocabulary_uuid, $term_label_callback = NULL, $empty_option = FALSE,
1512
                                  array $include_filter = null, $order_by = CDM_ORDER_BY_ORDER_INDEX_ASC) {
1513

    
1514
  static $vocabularyOptions = array();
1515

    
1516
  if (!isset($vocabularyOptions[$vocabulary_uuid])) {
1517
    $terms = cdm_ws_fetch_all('termVocabulary/' . $vocabulary_uuid . '/terms',
1518
      array(
1519
        'orderBy' => $order_by
1520
      )
1521
    );
1522

    
1523
    // apply the include filter
1524
    if($include_filter != null){
1525
      $included_terms = array();
1526

    
1527
      foreach ($terms as $term){
1528
        $include = true;
1529
        foreach ($include_filter as $field=>$regex){
1530
          $include =  preg_match($regex, var_export($term->$field, true)) === 1;
1531
          if(!$include){
1532
            break;
1533
          }
1534
        }
1535
        if($include){
1536
          $included_terms[] = $term;
1537
        }
1538
      }
1539

    
1540
      $terms = $included_terms;
1541
    }
1542

    
1543
    // make options list
1544
    $vocabularyOptions[$vocabulary_uuid] = cdm_terms_as_options($terms, $term_label_callback, $empty_option);
1545
  }
1546

    
1547
  $options = $vocabularyOptions[$vocabulary_uuid];
1548

    
1549
  return $options;
1550
}
1551

    
1552
/**
1553
 * Creates and array of defaults for drupal select form elements.
1554
 *
1555
 * @param $vocabulary_uuid
1556
 *   The UUID of the CDM Term Vocabulary
1557
 * @param $term_label_callback
1558
 *   An optional call back function which can be used to modify the term label
1559
 * @param bool $empty_option
1560
 *   An additional element do be placed at the beginning og the list. This element will be the default option.
1561
 * @param array $include_filter
1562
 *   An associative array consisting of a field name an regular expression. All term matching
1563
 *   these filter are included. The value of the field is converted to a String by var_export()
1564
 *   so a boolean 'true' can be matched by '/true/'
1565
 * @param string $order_by
1566
 *   One of the order by constants defined in this file
1567
 * @return array
1568
 *   the terms in an array (key: uuid => value: uuid) as defaults for a form element that allows multiple choices.
1569
 */
1570
function cdm_vocabulary_as_defaults($vocabulary_uuid, array $include_filter = null) {
1571

    
1572
  $options = cdm_vocabulary_as_option($vocabulary_uuid, null, null, $include_filter);
1573
  $defaults = array();
1574
  foreach ($options as $uuid => $value){
1575
    $defaults[$uuid] = $uuid;
1576
  }
1577

    
1578
  return $defaults;
1579
}
1580

    
1581
/**
1582
 * @param $term_type string one of
1583
 *  - Unknown
1584
 *  - Language
1585
 *  - NamedArea
1586
 *  - Rank
1587
 *  - Feature
1588
 *  - AnnotationType
1589
 *  - MarkerType
1590
 *  - ExtensionType
1591
 *  - DerivationEventType
1592
 *  - PresenceAbsenceTerm
1593
 *  - NomenclaturalStatusType
1594
 *  - NameRelationshipType
1595
 *  - HybridRelationshipType
1596
 *  - SynonymRelationshipType
1597
 *  - TaxonRelationshipType
1598
 *  - NameTypeDesignationStatus
1599
 *  - SpecimenTypeDesignationStatus
1600
 *  - InstitutionType
1601
 *  - NamedAreaType
1602
 *  - NamedAreaLevel
1603
 *  - RightsType
1604
 *  - MeasurementUnit
1605
 *  - StatisticalMeasure
1606
 *  - MaterialOrMethod
1607
 *  - Material
1608
 *  - Method
1609
 *  - Modifier
1610
 *  - Scope
1611
 *  - Stage
1612
 *  - KindOfUnit
1613
 *  - Sex
1614
 *  - ReferenceSystem
1615
 *  - State
1616
 *  - NaturalLanguageTerm
1617
 *  - TextFormat
1618
 *  - DeterminationModifier
1619
 *  - DnaMarker
1620
 *
1621
 * @param  $order_by
1622
 *  Optionally sort option (default: CDM_ORDER_BY_TITLE_CACHE_ASC)
1623
 *  possible values:
1624
 *    - CDM_ORDER_BY_ID_ASC
1625
 *    - CDM_ORDER_BY_ID_DESC
1626
 *    - CDM_ORDER_BY_TITLE_CACHE_ASC
1627
 *    - CDM_ORDER_BY_TITLE_CACHE_DESC
1628
 *    - CDM_ORDER_BY_ORDER_INDEX_ASC (can only be used with OrderedTerms!!)
1629
 *    - CDM_ORDER_BY_ORDER_INDEX_DESC (can only be used with OrderedTerms!!)
1630
 * @param bool $empty_option
1631
 *   An additional element do be placed at the beginning og the list. This element will be the default option.
1632
 * @return array
1633
 *    the terms in an array (key: uuid => value: label) as options for a form element that allows multiple choices.
1634
 */
1635
function cdm_terms_by_type_as_option($term_type, $order_by = CDM_ORDER_BY_TITLE_CACHE_ASC, $term_label_callback = NULL, $empty_option = FALSE){
1636
  $terms = cdm_ws_fetch_all(
1637
    CDM_WS_TERM,
1638
    array(
1639
      'class' => $term_type,
1640
      'orderBy' => $order_by
1641
    )
1642
  );
1643
  return cdm_terms_as_options($terms, $term_label_callback, $empty_option);
1644
}
1645

    
1646
/**
1647
 * @param array $none_option
1648
 *    Will add a filter option to search for NULL values
1649
 * @param $with_empty_option
1650
 *    Will add an empty option to the beginning. Choosing this option will disable the filtering.
1651
 * @return array
1652
 *   An array of options with uuids as key and the localized term representation as value
1653
 */
1654
function cdm_type_designation_status_filter_terms_as_options($none_option_label, $with_empty_option = false){
1655
  $filter_terms = cdm_ws_get(CDM_WS_TYPE_DESIGNATION_STATUS_FILTER_TERMS);
1656

    
1657
  $options = [];
1658

    
1659
  if(isset($filter_terms) && is_array($filter_terms)) {
1660
    foreach ($filter_terms as $filter_term) {
1661
      $options[join(',', $filter_term->uuids)] = $filter_term->label;
1662
    }
1663
  }
1664

    
1665
  if(is_string($none_option_label)){
1666
    $options = array_merge(array('NULL' => $none_option_label), $options);
1667
  }
1668

    
1669
  if($with_empty_option !== FALSE){
1670
    array_unshift ($options, "");
1671
  }
1672

    
1673
  return $options;
1674
}
1675

    
1676

    
1677

    
1678
/**
1679
 * Callback function which provides the localized representation of a cdm term.
1680
 *
1681
 * The representation is build by concatenating the abbreviated label with the label
1682
 * and thus is especially useful for relationship terms
1683
 * The localized representation provided by the cdm can be overwritten by
1684
 * providing a drupal translation.
1685
 *
1686
 */
1687
function _cdm_relationship_type_term_label_callback($term) {
1688
  if (isset($term->representation_L10n_abbreviatedLabel)) {
1689
    return $term->representation_L10n_abbreviatedLabel . ' : '
1690
    . t('@term', array('@term' => $term->representation_L10n));
1691
  }
1692
else {
1693
    return t('@term', array('@term' => $term->representation_L10n));
1694
  }
1695
}
1696

    
1697
/**
1698
 * Callback function which provides the localized inverse representation of a cdm term.
1699
 *
1700
 * The representation is build by concatenating the abbreviated label with the label
1701
 * and thus is especially useful for relationship terms
1702
 * The localized representation provided by the cdm can be overwritten by
1703
 * providing a drupal translation.
1704
 *
1705
 */
1706
function _cdm_relationship_type_term_inverse_label_callback($term) {
1707
  if (isset($term->inverseRepresentation_L10n_abbreviatedLabel)) {
1708
    return $term->inverseRepresentation_L10n_abbreviatedLabel . ' : '
1709
      . t('@term', array('@term' => $term->inverseRepresentation_L10n));
1710
  }
1711
  else {
1712
    return t('@term', array('@term' => $term->inverseRepresentation_L10n));
1713
  }
1714
}
1715

    
1716
/**
1717
 * Returns the localized abbreviated label of the relationship term.
1718
 *
1719
 * In case the abbreviated label is not set the normal representation is returned.
1720
 *
1721
 * @param $term
1722
 * @param bool $is_inverse_relation
1723
 * @return string
1724
 *   The abbreviated label
1725
 */
1726
function cdm_relationship_type_term_abbreviated_label($term, $is_inverse_relation = false){
1727

    
1728
  if($is_inverse_relation) {
1729
    if (isset($term->inverseRepresentation_L10n_abbreviatedLabel) && $term->inverseRepresentation_L10n_abbreviatedLabel) {
1730
      $abbr_label = $term->inverseResentation_L10n_abbreviatedLabel;
1731
    } else {
1732
      $abbr_label = $term->inverseRepresentation_L10n;
1733
    }
1734
  } else {
1735
    if (isset($term->representation_L10n_abbreviatedLabel) && $term->representation_L10n_abbreviatedLabel) {
1736
      $abbr_label = $term->representation_L10n_abbreviatedLabel;
1737
    } else {
1738
      $abbr_label = $term->representation_L10n;
1739
    }
1740
  }
1741
  return $abbr_label;
1742
}
1743

    
1744
/**
1745
 * Returns the symbol of the relationship term.
1746
 *
1747
 * In case the symbol is not set the function falls back to use the abbreviated label or
1748
 * the normal representation..
1749
 *
1750
 * @param $term
1751
 * @param bool $is_inverse_relation
1752
 * @return string
1753
 *   The abbreviated label
1754
 */
1755
function cdm_relationship_type_term_symbol($term, $is_inverse_relation = false){
1756

    
1757
  if($is_inverse_relation) {
1758
    if (isset($term->inverseSymbol) && $term->inverseSymbol) {
1759
      $symbol = $term->inverseSymbol;
1760
    } else if (isset($term->inverseRepresentation_L10n_abbreviatedLabel) && $term->inverseRepresentation_L10n_abbreviatedLabel) {
1761
      $symbol = $term->inverseResentation_L10n_abbreviatedLabel;
1762
    } else {
1763
      $symbol = $term->inverseRepresentation_L10n;
1764
    }
1765
  } else {
1766
    if (isset($term->symbol) && $term->symbol) {
1767
      $symbol = $term->symbol;
1768
    } else if (isset($term->representation_L10n_abbreviatedLabel) && $term->representation_L10n_abbreviatedLabel) {
1769
      $symbol = $term->representation_L10n_abbreviatedLabel;
1770
    } else {
1771
      $symbol = $term->representation_L10n;
1772
    }
1773
  }
1774
  return $symbol;
1775
}
1776

    
1777
// ========================================================================================== //
1778
/**
1779
 * @todo Improve documentation of this function.
1780
 *
1781
 * eu.etaxonomy.cdm.model.description.
1782
 * CategoricalData
1783
 * CommonTaxonName
1784
 * Distribution
1785
 * IndividualsAssociation
1786
 * QuantitativeData
1787
 * TaxonInteraction
1788
 * TextData
1789
 */
1790
function cdm_descriptionElementTypes_as_option($prependEmptyElement = FALSE) {
1791
  static $types = array(
1792
    "CategoricalData",
1793
    "CommonTaxonName",
1794
    "Distribution",
1795
    "IndividualsAssociation",
1796
    "QuantitativeData",
1797
    "TaxonInteraction",
1798
    "TextData",
1799
  );
1800

    
1801
  static $options = NULL;
1802
  if ($options == NULL) {
1803
    $options = array();
1804
    if ($prependEmptyElement) {
1805
      $options[' '] = '';
1806
    }
1807
    foreach ($types as $type) {
1808
      // No internatianalization here since these are purely technical terms.
1809
      $options["eu.etaxonomy.cdm.model.description." . $type] = $type;
1810
    }
1811
  }
1812
  return $options;
1813
}
1814

    
1815

    
1816
/**
1817
 * Fetches all TaxonDescription descriptions elements which are associated to the
1818
 * Taxon specified by the $taxon_uuid and merges the elements into the given
1819
 * feature tree.
1820
 * @param $feature_tree
1821
 *     The CDM FeatureTree to be used as template
1822
 * @param $taxon_uuid
1823
 *     The UUID of the taxon
1824
 * @param $excludes
1825
 *     UUIDs of features to be excluded
1826
 * @return object
1827
 *     The CDM FeatureTree which was given as parameter merged tree whereas the
1828
 *     CDM FeatureNodes are extended by an additional field 'descriptionElements'
1829
 *     witch will hold the according $descriptionElements.
1830
 */
1831
function cdm_ws_descriptions_by_featuretree($feature_tree, $taxon_uuid, $exclude_uuids = array()) {
1832

    
1833
  if (!$feature_tree) {
1834
    drupal_set_message(check_plain(t("No 'FeatureTree' has been set so far.
1835
      In order to see the species profiles of your taxa, please select a
1836
      'FeatureTree' in the !settings"), array('!settings' => l(t('CDM Dataportal Settings'), 'admin/config/cdm_dataportal/layout'))), 'warning');
1837
    return FALSE;
1838
  }
1839

    
1840
  $description_elements = cdm_ws_fetch_all(CDM_WS_DESCRIPTIONELEMENT_BY_TAXON,
1841
      array(
1842
      'taxon' => $taxon_uuid,
1843
      'features' => cdm_featureTree_elements_toString($feature_tree->root, ',', 'uuid', $exclude_uuids)
1844
      ),
1845
      'POST'
1846
  );
1847

    
1848
  // Combine all descriptions into one feature tree.
1849
  $merged_nodes = _mergeFeatureTreeDescriptions($feature_tree->root->childNodes, $description_elements);
1850
  $feature_tree->root->childNodes = $merged_nodes;
1851

    
1852
  return $feature_tree;
1853
}
1854

    
1855
/**
1856
 * Returns a filtered a list of annotations for the cdm entity given as parameter $cdm_entity.
1857
 * If the annotations are not yet already loded with the cdm entity the cdm REST service will
1858
 * be requested for the annotations.
1859
 *
1860
 * @param string $cdm_entity_or_dto
1861
 *   An annotatable cdm entity, or corresponding DTO, DTOs containig AnnotationDTOs
1862
 *     in an array field named 'annotations' is supported by this method.
1863
 * @param array $include_types
1864
 *   If an array of annotation type uuids is supplied by this parameter the
1865
 *   list of annotations is resticted to those which belong to this type.
1866
 *
1867
 * @return array
1868
 *   An array of Annotation objects or an empty array.
1869
 */
1870
function cdm_ws_fetch_annotations(&$cdm_entity_or_dto, $include_types = FALSE) {
1871

    
1872
  if(!isset($cdm_entity_or_dto->annotations)){
1873
    $annotation_url = cdm_compose_annotations_uri($cdm_entity_or_dto);
1874
    $cdm_entity_or_dto->annotations = cdm_ws_fetch_all($annotation_url, array(), 'GET', TRUE);
1875
  }
1876

    
1877
  $annotations_or_dtos = array();
1878
  foreach ($cdm_entity_or_dto->annotations as $annotation_or_dto) {
1879
    if ($include_types) {
1880
      $is_dto = is_cdm_dto($annotation_or_dto);
1881
      if (($is_dto ? annotation_dto_has_type($annotation_or_dto, $include_types) : annotation_has_type($annotation_or_dto, $include_types))) {
1882
        $annotations_or_dtos[] = $annotation_or_dto;
1883
      }
1884
    }
1885
    else {
1886
      $annotations_or_dtos[] = $annotation_or_dto;
1887
    }
1888
  }
1889
  return $annotations_or_dtos;
1890
}
1891

    
1892
/**
1893
 * @param $annotation
1894
 * @param array $include_types
1895
 *
1896
 * @return bool
1897
 */
1898
function annotation_has_type($annotation, array $include_types) {
1899
  return (isset($annotation->annotationType->uuid) && in_array($annotation->annotationType->uuid, $include_types, TRUE))
1900
    || ($annotation->annotationType === NULL && in_array('NULL_VALUE', $include_types, TRUE));
1901
}
1902

    
1903
/**
1904
 * @param $annotation_dto
1905
 * @param array $include_types
1906
 *
1907
 * @return bool
1908
 */
1909
function annotation_dto_has_type($annotation_dto, array $include_types) {
1910
  return (isset($annotation_dto->annotationTypeUuid) && in_array($annotation_dto->annotationTypeUuid, $include_types, TRUE))
1911
    || ($annotation_dto->annotationTypeUuid === NULL && in_array('NULL_VALUE', $include_types, TRUE));
1912
}
1913

    
1914
/**
1915
 * Tests if the passed object is a DTO exposed by the cdm webservices.
1916
 * All these DTOs contain a field `class` with the Java class simple name
1917
 * as value. As all DTO classes are suffixed with "DTO" this method can test
1918
 * for this class name suffix.
1919
 *
1920
 * @param $cdm_dto
1921
 *  The potential cdm dto to test
1922
 *
1923
 * @return bool
1924
 *  True in case the $cdm_dto is a DTO
1925
 */
1926
function is_cdm_dto(&$cdm_dto) {
1927
  return isset_not_empty($cdm_dto->type) && isset_not_empty($cdm_dto->class) && str_endsWith($cdm_dto->class , 'DTO');
1928
}
1929

    
1930
/**
1931
 * Provides the list of visible annotations for the $cdm_entity.
1932
 *
1933
 * @param $cdm_entity_or_dto
1934
 *     The annotatable CDM entity or corresponding DTO, DTOs containig AnnotationDTOs
1935
 *     in an array field named 'annotations' is supported by this method
1936
 *
1937
 * @return array of the annotations entities or DTOs which are visible according
1938
 *     to the settings as stored in ANNOTATION_TYPES_VISIBLE
1939
 */
1940
function cdm_fetch_visible_annotations($cdm_entity_or_dto){
1941

    
1942
  static $annotations_types_filter = null;
1943
  if(!$annotations_types_filter) {
1944
    $annotations_types_filter = unserialize(EXTENSION_TYPES_VISIBLE_DEFAULT);
1945
  }
1946
  return cdm_ws_fetch_annotations($cdm_entity_or_dto, variable_get(ANNOTATION_TYPES_VISIBLE, $annotations_types_filter));
1947
}
1948

    
1949
function cdm_load_tagged_full_title($taxon_name){
1950
  if(isset($taxon_name) && !isset($taxon_name->taggedFullTitle)){
1951
    $tagged_full_title = cdm_ws_get(CDM_WS_NAME, array($taxon_name->uuid, 'taggedFullTitle'));
1952
    if(is_array($tagged_full_title)){
1953
      $taxon_name->taggedFullTitle = $tagged_full_title;
1954

    
1955
    }
1956
  }
1957
}
1958

    
1959
/**
1960
 * Extends the $cdm_entity object by the field if it is not already existing.
1961
 *
1962
 * This function can only be used for fields with 1 to many relations.
1963
  *
1964
 * @param $cdm_base_type
1965
 * @param $field_name
1966
 * @param $cdm_entity
1967
 */
1968
function cdm_lazyload_array_field($cdm_base_type, $field_name, &$cdm_entity)
1969
{
1970
  if (!isset($cdm_entity->$field_name)) {
1971
    $items = cdm_ws_fetch_all('portal/' . $cdm_base_type . '/' . $cdm_entity->uuid . '/' . $field_name);
1972
    $cdm_entity->$field_name = $items;
1973
  }
1974
}
1975

    
1976

    
1977
/**
1978
 * Get a NomenclaturalReference string.
1979
 *
1980
 * Returns the NomenclaturalReference string with correctly placed
1981
 * microreference (= reference detail) e.g.
1982
 * in Phytotaxa 43: 1-48. 2012.
1983
 *
1984
 * @param string $referenceUuid
1985
 *   UUID of the reference.
1986
 * @param string $microreference
1987
 *   Reference detail.
1988
 *
1989
 * @return string
1990
 *   a NomenclaturalReference.
1991
 */
1992
function cdm_ws_getNomenclaturalReference($referenceUuid, $microreference) {
1993

    
1994
  // TODO the below statement avoids error boxes due to #4644 remove it once this ticket is solved
1995
  if(is_array($microreference) || is_object($microreference)) {
1996
    return '';
1997
  }
1998

    
1999
  $obj = cdm_ws_get(CDM_WS_NOMENCLATURAL_REFERENCE_CITATION, array(
2000
    $referenceUuid,
2001
  ), "microReference=" . urlencode($microreference));
2002

    
2003
  if ($obj) {
2004
    return $obj->String;
2005
  }
2006
  else {
2007
    return NULL;
2008
  }
2009
}
2010

    
2011
/**
2012
 * finds and returns the FeatureNode denoted by the given $feature_uuid
2013
 *
2014
 * @param $feature_tree_nodes
2015
 *    The nodes contained in CDM FeatureTree entitiy: $feature->root->childNodes
2016
 * @param $feature_uuid
2017
 *    The UUID of the Feature
2018
 * @return object
2019
 *    the FeatureNode or null
2020
 */
2021
function &cdm_feature_tree_find_node($feature_tree_nodes, $feature_uuid){
2022

    
2023
  // 1. scan this level
2024
  foreach ($feature_tree_nodes as $node){
2025
    if($node->term->uuid == $feature_uuid){
2026
      return $node;
2027
    }
2028
  }
2029

    
2030
  // 2. descend into childen
2031
  foreach ($feature_tree_nodes as $node){
2032
    if(is_array($node->childNodes)){
2033
      $node = cdm_feature_tree_find_node($node->childNodes, $feature_uuid);
2034
      if($node) {
2035
        return $node;
2036
      }
2037
    }
2038
  }
2039
  $null_var = null; // kludgy workaround to avoid "PHP Notice: Only variable references should be returned by reference"
2040
  return $null_var;
2041
}
2042

    
2043
/**
2044
 * Merges the given featureNodes structure with the descriptionElements.
2045
 *
2046
 * This method is used in preparation for rendering the descriptionElements.
2047
 * The descriptionElements which belong to a specific feature node are appended
2048
 * to a the feature node by creating a new field:
2049
 *  - descriptionElements: the CDM DescriptionElements which belong to this feature
2050
 * The descriptionElements will be cleared in advance in order to allow reusing the
2051
 * same feature tree without the risk of mixing sets of description elements.
2052
 *
2053
 * which originally is not existing in the cdm.
2054
 *
2055
 *
2056
 *
2057
 * @param array $featureNodes
2058
 *    An array of cdm FeatureNodes which may be hierarchical since feature nodes
2059
 *    may have children.
2060
 * @param array $descriptionElements
2061
 *    An flat array of cdm DescriptionElements
2062
 * @return array
2063
 *    The $featureNodes structure enriched with the according $descriptionElements
2064
 */
2065
function _mergeFeatureTreeDescriptions($featureNodes, $descriptionElements) {
2066

    
2067
  foreach ($featureNodes as &$feature_node) {
2068
    // since the $featureNodes array is reused for each description
2069
    // it is necessary to clear the custom node fields in advance
2070
    if(isset($feature_node->descriptionElements)){
2071
      unset($feature_node->descriptionElements);
2072
    }
2073

    
2074
    // Append corresponding elements to an additional node field:
2075
    // $node->descriptionElements.
2076
    foreach ($descriptionElements as $element) {
2077
      if ($element->feature->uuid == $feature_node->term->uuid) {
2078
        if (!isset($feature_node->descriptionElements)) {
2079
          $feature_node->descriptionElements = array();
2080
        }
2081
        $feature_node->descriptionElements[] = $element;
2082
      }
2083
    }
2084

    
2085
    // Recurse into node children.
2086
    if (isset($feature_node->childNodes[0])) {
2087
      $mergedChildNodes = _mergeFeatureTreeDescriptions($feature_node->childNodes, $descriptionElements);
2088
      $feature_node->childNodes = $mergedChildNodes;
2089
    }
2090

    
2091
    if(!isset($feature_node->descriptionElements) && !isset($feature_node->childNodes[0])){
2092
      unset($feature_node);
2093
    }
2094

    
2095
  }
2096

    
2097
  return $featureNodes;
2098
}
2099

    
2100
/**
2101
 * Sends a GET or POST request to a CDM RESTService and returns a de-serialized object.
2102
 *
2103
 * The response from the HTTP GET request is returned as object.
2104
 * The response objects coming from the webservice configured in the
2105
 * 'cdm_webservice_url' variable are being cached in a level 1 (L1) and / or
2106
 *  in a level 2 (L2) cache.
2107
 *
2108
 * Since the L1 cache is implemented as static variable of the cdm_ws_get()
2109
 * function, this cache persists only per each single page execution.
2110
 * Any object coming from the webservice is stored into it by default.
2111
 * In contrast to this default caching mechanism the L2 cache only is used if
2112
 * the 'cdm_webservice_cache' variable is set to TRUE,
2113
 * which can be set using the modules administrative settings section.
2114
 * Objects stored in this L2 cache are serialized and stored
2115
 * using the drupal cache in the '{prefix}cache_cdm_ws' cache table. So the
2116
 * objects that are stored in the database will persist as
2117
 * long as the drupal cache is not being cleared and are available across
2118
 * multiple script executions.
2119
 *
2120
 * @param string $uri
2121
 *   URL to the webservice.
2122
 * @param array $pathParameters
2123
 *   An array of path parameters.
2124
 * @param string $query_string
2125
 *   A query_string string to be appended to the URL.
2126
 * @param string $method
2127
 *   The HTTP method to use, valid values are "GET" or "POST";
2128
 * @param bool $absoluteURI
2129
 *   TRUE when the URL should be treated as absolute URL.
2130
 *
2131
 * @return object| array
2132
 *   The de-serialized webservice response object.
2133
 */
2134
function cdm_ws_get($uri, $pathParameters = array(), $query_string = NULL, $method = "GET", $absoluteURI = FALSE) {
2135

    
2136
  static $cacheL1 = array();
2137

    
2138
  $data = NULL;
2139
  // store query_string string in $data and clear the query_string, $data will be set as HTTP request body
2140
  if($method == 'POST'){
2141
    $data = $query_string;
2142
    $query_string = NULL;
2143
  }
2144

    
2145
  // Transform the given uri path or pattern into a proper webservice uri.
2146
  if (!$absoluteURI) {
2147
    $uri = cdm_compose_ws_url($uri, $pathParameters, $query_string);
2148
  } else {
2149
    if($query_string){
2150
      $uri = append_query_parameters($uri, $query_string);
2151
    }
2152
  }
2153
  cdm_ws_apply_classification_subtree_filter($uri);
2154

    
2155
  // read request parameter 'cacheL2_refresh'
2156
  // which allows refreshing the level 2 cache
2157
  $do_cacheL2_refresh = isset($_REQUEST['cacheL2_refresh']) && $_REQUEST['cacheL2_refresh'] == 1;
2158

    
2159
  $is_cdm_ws_uri = _is_cdm_ws_uri($uri);
2160
  $use_cacheL2 = variable_get('cdm_webservice_cache', 1);
2161

    
2162
  if($method == 'GET'){
2163
    $cache_key = $uri;
2164
  } else {
2165
    // sha1 creates longer hashes and thus will cause fewer collisions than md5.
2166
    // crc32 is faster but creates much shorter hashes
2167
    $cache_key = $uri . '[' . $method . ':' . sha1($data) .']';
2168
  }
2169

    
2170
  if (array_key_exists($cache_key, $cacheL1)) {
2171
    $cacheL1_obj = $cacheL1[$uri];
2172
  }
2173

    
2174
  $set_cacheL1 = FALSE;
2175
  if ($is_cdm_ws_uri && !isset($cacheL1_obj)) {
2176
    $set_cacheL1 = TRUE;
2177
  }
2178

    
2179
  // Only cache cdm webservice URIs.
2180
  $set_cacheL2 = $use_cacheL2 && $is_cdm_ws_uri && $set_cacheL1;
2181
  $cacheL2_entry = FALSE;
2182

    
2183
  if ($use_cacheL2 && !$do_cacheL2_refresh) {
2184
    // Try to get object from cacheL2.
2185
    $cacheL2_entry = cache_get($cache_key, 'cache_cdm_ws');
2186
  }
2187

    
2188
  if (isset($cacheL1_obj)) {
2189
    //
2190
    // The object has been found in the L1 cache.
2191
    //
2192
    $obj = $cacheL1_obj;
2193
    if (cdm_debug_block_visible()) {
2194
      cdm_ws_debug_add($uri, $method, $data, 0, 0, NULL, 'cacheL1');
2195
    }
2196
  }
2197
  elseif ($cacheL2_entry) {
2198
    //
2199
    // The object has been found in the L2 cache.
2200
    //
2201
    $duration_parse_start = microtime(TRUE);
2202
    $obj = unserialize($cacheL2_entry->data);
2203
    $duration_parse = microtime(TRUE) - $duration_parse_start;
2204

    
2205
    if (cdm_debug_block_visible()) {
2206
      cdm_ws_debug_add($uri, $method, $data, 0, $duration_parse, NULL, 'cacheL2');
2207
    }
2208
  }
2209
  else {
2210
    //
2211
    // Get the object from the webservice and cache it.
2212
    //
2213
    $duration_fetch_start = microtime(TRUE);
2214
    // Request data from webservice JSON or XML.
2215
    $response = cdm_http_request($uri, $method, $data);
2216
    $response_body = NULL;
2217
    if (isset($response->data)) {
2218
      $response_body = $response->data;
2219
    }
2220
    $duration_fetch = microtime(TRUE) - $duration_fetch_start;
2221
    $duration_parse_start = microtime(TRUE);
2222

    
2223
    // Parse data and create object.
2224
    $obj = cdm_load_obj($response_body);
2225

    
2226
    if(isset($obj->servlet) && isset($obj->status) && is_numeric($obj->status)){
2227
      // this is json error message returned by jetty #8914
2228
      // wee need to replace it by null to avoid breaking existing assumptions in the code here
2229
      // this is also related to #2711
2230
      $obj = null;
2231
    }
2232

    
2233
    $duration_parse = microtime(TRUE) - $duration_parse_start;
2234

    
2235
    if (cdm_debug_block_visible()) {
2236
      if ($obj || $response_body == "[]") {
2237
        $status = 'valid';
2238
      }
2239
      else {
2240
        $status = 'invalid';
2241
      }
2242
      cdm_ws_debug_add($uri, $method, $data, $duration_fetch, $duration_parse, strlen($response_body), $status);
2243
    }
2244
    if ($set_cacheL2) {
2245
      // Store the object in cache L2.
2246
      // Comment @WA perhaps better if Drupal serializedatas here? Then the
2247
      // flag serialized is set properly in the cache table.
2248
      cache_set($cache_key, serialize($obj), 'cache_cdm_ws', CACHE_TEMPORARY);
2249
    }
2250
  }
2251
  if ($obj) {
2252
    // Store the object in cache L1.
2253
    if ($set_cacheL1) {
2254
      $cacheL1[$cache_key] = $obj;
2255
    }
2256
  }
2257
  return $obj;
2258
}
2259

    
2260
function cdm_ws_apply_classification_subtree_filter(&$uri){
2261

    
2262
  $classification_subtree_filter_patterns = &drupal_static('classification_subtree_filter_patterns', array(
2263
    "#/classification/[0-9a-f\-]{36}/childNodes#",
2264
    /* covered by above pattern:
2265
    "#/classification/[0-9a-f\-]{36}/childNodesAt/[0-9a-f\-]{36}#",
2266
    '#/classification/[0-9a-f\-]{36}/childNodesOf/[0-9a-f\-]{36}#',
2267
    */
2268
    "#/portal/classification/[0-9a-f\-]{36}/childNodes#",
2269
    /* covered by above pattern:
2270
    "#/portal/classification/[0-9a-f\-]{36}/childNodesAt/[0-9a-f\-]{36}#",
2271
    '#/portal/classification/[0-9a-f\-]{36}/childNodesOf/[0-9a-f\-]{36}#',
2272
    */
2273
    '#/portal/classification/[0-9a-f\-]{36}/pathFrom/[0-9a-f\-]{36}#',
2274
    "#/portal/taxon/search#",
2275
    "#/portal/taxon/find#",
2276
    /* covered by above pattern:
2277
    "#/portal/taxon/findByDescriptionElementFullText#",
2278
    "#/portal/taxon/findByFullText#",
2279
    "#/portal/taxon/findByEverythingFullText#",
2280
    "#/portal/taxon/findByIdentifier#",
2281
    "#/portal/taxon/findByMarker#",
2282
    "#/portal/taxon/findByMarker#",
2283
    "#/portal/taxon/findByMarker#",
2284
    */
2285
    "#/portal/taxon/[0-9a-f\-]{36}#"
2286
    /* covered by above pattern:
2287
    "#/portal/taxon/[0-9a-f\-]{36}/taxonNodes#",
2288
    */
2289
  ));
2290

    
2291
  $sub_tree_filter_uuid_value = variable_get(CDM_SUB_TREE_FILTER_UUID, FALSE);
2292
  if(is_uuid($sub_tree_filter_uuid_value)){
2293
    foreach($classification_subtree_filter_patterns as $preg_pattern){
2294
      if(preg_match($preg_pattern, $uri)){
2295
        // no need to take care for uri fragments with ws uris!
2296
        if(strpos( $uri, '?')){
2297
          $uri .= '&subtree=' . $sub_tree_filter_uuid_value;
2298
        } else {
2299
          $uri .= '?subtree='. $sub_tree_filter_uuid_value;
2300
        }
2301
        break;
2302
      }
2303
    }
2304
  }
2305

    
2306
}
2307
/**
2308
 * Processes and stores the given information in $_SESSION['cdm']['ws_debug'] as table row.
2309
 *
2310
 * The cdm_ws_debug block will display the debug information.
2311
 *
2312
 * @param $uri
2313
 *    The CDM REST URI to which the request has been send
2314
 * @param string $method
2315
 *    The HTTP request method, either 'GET' or 'POST'
2316
 * @param string $post_data
2317
 *    The datastring send with a post request
2318
 * @param $duration_fetch
2319
 *    The time in seconds it took to fetch the data from the web service
2320
 * @param $duration_parse
2321
 *    Time in seconds which was needed to parse the json response
2322
 * @param $datasize
2323
 *    Size of the data received from the server
2324
 * @param $status
2325
 *    A status string, possible values are: 'valid', 'invalid', 'cacheL1', 'cacheL2'
2326
 * @return bool
2327
 *    TRUE if adding the debug information was successful
2328
 */
2329
function cdm_ws_debug_add($uri, $method, $post_data, $duration_fetch, $duration_parse, $datasize, $status) {
2330

    
2331
  static $initial_time = NULL;
2332
  if(!$initial_time) {
2333
    $initial_time = microtime(TRUE);
2334
  }
2335
  $time = microtime(TRUE) - $initial_time;
2336

    
2337
  // Decompose uri into path and query element.
2338
  $uri_parts = explode("?", $uri);
2339
  $query = array();
2340
  if (count($uri_parts) == 2) {
2341
    $path = $uri_parts[0];
2342
  }
2343
  else {
2344
    $path = $uri;
2345
  }
2346

    
2347
  if(strpos($uri, '?') > 0){
2348
    $json_uri = str_replace('?', '.json?', $uri);
2349
    $xml_uri = str_replace('?', '.xml?', $uri);
2350
  } else {
2351
    $json_uri = $uri . '.json';
2352
    $xml_uri = $json_uri . '.xml';
2353
  }
2354

    
2355
  // data links to make data accessible as json and xml
2356
  $data_links = '';
2357
  if (_is_cdm_ws_uri($path)) {
2358

    
2359
    // see ./js/http-method-link.js
2360

    
2361
    if($method == 'GET'){
2362
      $data_links .= '<a href="' . $xml_uri . '" target="data">xml</a>-';
2363
      $data_links .= '<a href="' . url('cdm_api/proxy/' . urlencode($xml_uri)) . '" target="data">proxied</a>';
2364
      $data_links .= '<br/>';
2365
      $data_links .= '<a href="' . $json_uri . '" target="data">json</a>-';
2366
      $data_links .= '<a href="' . url('cdm_api/proxy/' . urlencode($json_uri)) . '" target="data">proxied</a>';
2367
    } else {
2368
      $js_link_activation = 'class="http-' . $method . '-link" data-cdm-http-post="' . $post_data . '" type="application/x-www-form-urlencoded"';
2369
      $data_links .= '<a ' . $js_link_activation . ' href="' . url('cdm_api/proxy/' . urlencode($xml_uri)) . '" target="data">xml-proxied</a>';
2370
      $data_links .= '<br/>';
2371
      $data_links .= '<a ' . $js_link_activation . ' href="' . url('cdm_api/proxy/' . urlencode($json_uri)) . '" target="data">json-proxied</a>';
2372
    }
2373
  }
2374
  else {
2375
    $data_links .= '<a href="' . $uri . '" target="data">open</a>';
2376
  }
2377

    
2378
  //
2379
  $data = array(
2380
      'ws_uri' => $uri,
2381
      'method' => $method,
2382
      'post_data' => $post_data,
2383
      'time' => sprintf('%3.3f', $time),
2384
      'fetch_seconds' => sprintf('%3.3f', $duration_fetch),
2385
      'parse_seconds' => sprintf('%3.3f', $duration_parse),
2386
      'size_kb' => sprintf('%3.1f', ($datasize / 1024)) ,
2387
      'status' => $status,
2388
      'data_links' => $data_links
2389
  );
2390
  if (!isset($_SESSION['cdm']['ws_debug'])) {
2391
    $_SESSION['cdm']['ws_debug'] = array();
2392
  }
2393
  $_SESSION['cdm']['ws_debug'][] = serialize($data);
2394

    
2395
  // Mark this page as being uncacheable.
2396
  // taken over from drupal_get_messages() but it is unsure if we really need this here
2397
  drupal_page_is_cacheable(FALSE);
2398

    
2399
  // Messages not set when DB connection fails.
2400
  return isset($_SESSION['cdm']['ws_debug']) ? $_SESSION['cdm']['ws_debug'] : NULL;
2401
}
2402

    
2403
/**
2404
 * helper function to dtermine if the cdm_debug_block should be displayed or not
2405
 * the visibility depends on whether
2406
 *  - the block is enabled
2407
 *  - the visibility restrictions in the block settings are satisfied
2408
 */
2409
function cdm_debug_block_visible() {
2410
  static $is_visible = null;
2411

    
2412
  if($is_visible === null){
2413
      $block = block_load('cdm_api', 'cdm_ws_debug');
2414
      $is_visible = isset($block->status) && $block->status == 1;
2415
      if($is_visible){
2416
        $blocks = array($block);
2417
        // Checks the page, user role, and user-specific visibilty settings.
2418
        block_block_list_alter($blocks);
2419
        $is_visible = count($blocks) > 0;
2420
      }
2421
  }
2422
  return $is_visible;
2423
}
2424

    
2425
/**
2426
 * @todo Please document this function.
2427
 * @see http://drupal.org/node/1354
2428
 */
2429
function cdm_load_obj($response_body) {
2430
  $obj = json_decode($response_body);
2431

    
2432
  if (!(is_object($obj) || is_array($obj))) {
2433
    ob_start();
2434
    $obj_dump = ob_get_contents();
2435
    ob_clean();
2436
    return FALSE;
2437
  }
2438

    
2439
  return $obj;
2440
}
2441

    
2442
/**
2443
 * Do a http request to a CDM RESTful web service.
2444
 *
2445
 * @param string $uri
2446
 *   The webservice url.
2447
 * @param string $method
2448
 *   The HTTP method to use, valid values are "GET" or "POST"; defaults to
2449
 *   "GET" even if NULL, FALSE or any invalid value is supplied.
2450
 * @param $data: A string containing the request body, formatted as
2451
 *     'param=value&param=value&...'. Defaults to NULL.
2452
 *
2453
 * @return object
2454
 *   The object as returned by drupal_http_request():
2455
 *   An object that can have one or more of the following components:
2456
 *   - request: A string containing the request body that was sent.
2457
 *   - code: An integer containing the response status code, or the error code
2458
 *     if an error occurred.
2459
 *   - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
2460
 *   - status_message: The status message from the response, if a response was
2461
 *     received.
2462
 *   - redirect_code: If redirected, an integer containing the initial response
2463
 *     status code.
2464
 *   - redirect_url: If redirected, a string containing the URL of the redirect
2465
 *     target.
2466
 *   - error: If an error occurred, the error message. Otherwise not set.
2467
 *   - headers: An array containing the response headers as name/value pairs.
2468
 *     HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
2469
 *     easy access the array keys are returned in lower case.
2470
 *   - data: A string containing the response body that was received.
2471
 */
2472
function cdm_http_request($uri, $method = "GET", $data = NULL) {
2473
  static $acceptLanguage = NULL;
2474
  $header = array();
2475
  
2476
  if(!$acceptLanguage && module_exists('i18n')){
2477
    $acceptLanguage = i18n_language_content()->language;
2478
  }
2479

    
2480
  if (!$acceptLanguage) {
2481
    if (function_exists('apache_request_headers')) {
2482
      $headers = apache_request_headers();
2483
      if (isset($headers['Accept-Language'])) {
2484
        $acceptLanguage = $headers['Accept-Language'];
2485
      }
2486
    }
2487
  }
2488

    
2489
  if ($method != "GET" && $method != "POST") {
2490
    drupal_set_message('cdm_api.module#cdm_http_request() : unsupported HTTP request method ', 'error');
2491
  }
2492

    
2493
  if (_is_cdm_ws_uri($uri)) {
2494
    $header['Accept'] = 'application/json';
2495
    $header['Accept-Language'] = $acceptLanguage;
2496
    $header['Accept-Charset'] = 'UTF-8';
2497
  }
2498

    
2499
  if($method == "POST") {
2500
    // content type is application/x-www-form-urlencoded, so the request body uses the same format as the query string
2501
    $header['Content-Type'] = 'application/x-www-form-urlencoded';
2502
  }
2503

    
2504
  $context_resource = null;
2505
  if(!variable_get('cdm_webservice_url_ssl_verify', 1)){
2506
    $context_resource = stream_context_create(array('ssl' => array('verify_peer' => FALSE, 'verify_peer_name' => FALSE)));
2507
  }
2508
  cdm_dd($uri);
2509
  return drupal_http_request($uri, array(
2510
      'headers' => $header,
2511
      'method' => $method,
2512
      'data' => $data,
2513
      'timeout' => CDM_HTTP_REQUEST_TIMEOUT,
2514
      'context' => $context_resource
2515
      )
2516
   );
2517
}
2518

    
2519
/**
2520
 * Concatenates recursively the fields of all features contained in the given
2521
 * CDM FeatureTree root node.
2522
 *
2523
 * @param $rootNode
2524
 *     A CDM FeatureTree node
2525
 * @param
2526
 *     The character to be used as glue for concatenation, default is ', '
2527
 * @param $field_name
2528
 *     The field name of the CDM Features
2529
 * @param $excludes
2530
 *     Allows defining a set of values to be excluded. This refers to the values
2531
 *     in the field denoted by the $field_name parameter
2532
 *
2533
 */
2534
function cdm_featureTree_elements_toString($root_node, $separator = ', ', $field_name = 'representation_L10n', $excludes = array()) {
2535
  $out = '';
2536

    
2537
  $pre_child_separator = $separator;
2538
  $post_child_separator = '';
2539

    
2540
  foreach ($root_node->childNodes as $feature_node) {
2541
    $out .= ($out ? $separator : '');
2542
    if(!in_array($feature_node->term->$field_name, $excludes)) {
2543
      $out .= $feature_node->term->$field_name;
2544
      if (is_array($feature_node->childNodes) && count($feature_node->childNodes) > 0) {
2545
        $childlabels = cdm_featureTree_elements_toString($feature_node, $separator, $field_name);
2546
        if (strlen($childlabels)) {
2547
            $out .=  $pre_child_separator . $childlabels . $post_child_separator;
2548
        }
2549
      }
2550
    }
2551
  }
2552
  return $out;
2553
}
2554

    
2555
/**
2556
 * Create a one-dimensional form options array.
2557
 *
2558
 * Creates an array of all features in the feature tree of feature nodes,
2559
 * the node labels are indented by $node_char and $childIndent depending on the
2560
 * hierachy level.
2561
 *
2562
 * @param - $rootNode
2563
 * @param - $node_char
2564
 * @param - $childIndentStr
2565
 * @param - $childIndent
2566
 *   ONLY USED INTERNALLY!
2567
 *
2568
 * @return array
2569
 *   A one dimensional Drupal form options array.
2570
 */
2571
function _featureTree_nodes_as_feature_options($rootNode, $node_char = "&#9500;&#9472; ", $childIndentStr = '&nbsp;', $childIndent = '') {
2572
  $options = array();
2573
  foreach ($rootNode->childNodes as $featureNode) {
2574
    $indent_prefix = '';
2575
    if ($childIndent) {
2576
      $indent_prefix = $childIndent . $node_char . " ";
2577
    }
2578
    $options[$featureNode->term->uuid] = $indent_prefix . $featureNode->term->representation_L10n;
2579
    if (isset($featureNode->childNodes) && is_array($featureNode->childNodes)) {
2580
      // Foreach ($featureNode->childNodes as $childNode){
2581
      $childList = _featureTree_nodes_as_feature_options($featureNode, $node_char, $childIndentStr, $childIndent . $childIndentStr);
2582
      $options = array_merge_recursive($options, $childList);
2583
      // }
2584
    }
2585
  }
2586
  return $options;
2587
}
2588

    
2589
/**
2590
 * Returns an array with all available FeatureTrees and the representations of the selected
2591
 * FeatureTree as a detail view.
2592
 *
2593
 * @param boolean $add_default_feature_free
2594
 * @param boolean $show_weight
2595
 *     Show the weight which will be applied to the according feature block
2596
 * @return array
2597
 *  associative array with following keys:
2598
 *  -options: Returns an array with all available Feature Trees
2599
 *  -treeRepresentations: Returns representations of the selected Feature Tree as a detail view
2600
 *
2601
 */
2602
function cdm_get_featureTrees_as_options($add_default_feature_free = FALSE, $show_weight = FALSE) {
2603

    
2604
  $options = array();
2605
  $tree_representations = array();
2606
  $feature_trees = array();
2607

    
2608
  // Set tree that contains all features.
2609
  if ($add_default_feature_free) {
2610
    $options[UUID_DEFAULT_FEATURETREE] = t('Default Featuretree (contains all features)');
2611
    $feature_trees[] = cdm_ws_get(CDM_WS_TERMTREE, UUID_DEFAULT_FEATURETREE);
2612
  }
2613

    
2614
  // Get feature trees from database.
2615
  $persited_trees = cdm_ws_fetch_all(CDM_WS_TERMTREES, array("termType" => "Feature"));
2616
  if (is_array($persited_trees)) {
2617
    $feature_trees = array_merge($feature_trees, $persited_trees);
2618
  }
2619

    
2620
  foreach ($feature_trees as $featureTree) {
2621

    
2622
    if(!is_object($featureTree)){
2623
      continue;
2624
    }
2625
    // Do not add the DEFAULT_FEATURETREE again,
2626
    if ($featureTree->uuid != UUID_DEFAULT_FEATURETREE) {
2627
      $options[$featureTree->uuid] = $featureTree->representation_L10n;
2628
    }
2629

    
2630
    // Render the hierarchic tree structure
2631
    if (is_array( $featureTree->root->childNodes) && count( $featureTree->root->childNodes) > 0) {
2632

    
2633
      // Render the hierarchic tree structure.
2634
      $treeDetails = '<div class="featuretree_structure">'
2635
        . render_feature_tree_hierarchy($featureTree->uuid, $show_weight)
2636
        . '</div>';
2637

    
2638
      $form = array();
2639
      $form['featureTree-' .  $featureTree->uuid] = array(
2640
        '#type' => 'fieldset',
2641
        '#title' => 'Show details',
2642
        '#attributes' => array('class' => array('collapsible collapsed')),
2643
        // '#collapsible' => TRUE,
2644
        // '#collapsed' => TRUE,
2645
      );
2646
      $form['featureTree-' .  $featureTree->uuid]['details'] = array(
2647
        '#markup' => $treeDetails,
2648
      );
2649

    
2650
      $tree_representations[$featureTree->uuid] = drupal_render($form);
2651
    }
2652

    
2653
  } // END loop over feature trees
2654

    
2655
  // return $options;
2656
  return array('options' => $options, 'treeRepresentations' => $tree_representations);
2657
}
2658

    
2659
/**
2660
 * Provides the list of available classifications in form of an options array.
2661
 *
2662
 * The options array is suitable for drupal form API elements that allow multiple choices.
2663
 * @see http://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7#options
2664
 *
2665
 * The classifications are ordered alphabetically whereas the classification
2666
 * chosen as default will always appear on top of the array, followed by a
2667
 * blank line below.
2668
 *
2669
 * @param bool $add_none_option
2670
 *   is true an additional 'none' option will be added if and only if there are
2671
 *   more than one options. Defaults to FALSE
2672
 *
2673
 * @param $include_uuids
2674
 *   The taxon tree uuids to be included, other taxon trees will be filtered out.
2675
 *   You may want to use here:
2676
 *   variable_get(CDM_TAXONTREE_INCLUDES, [])
2677
 *
2678
 *
2679
 * @return array
2680
 *   classifications in an array as options for a form element that allows multiple choices.
2681
 */
2682
function cdm_get_taxontrees_as_options($add_none_option = FALSE, $include_uuids = []) {
2683

    
2684
  $taxonTrees = cdm_ws_fetch_all(CDM_WS_PORTAL_TAXONOMY);
2685

    
2686
  $default_classification_uuid = variable_get(CDM_TAXONOMICTREE_UUID, FALSE);
2687
  $default_classification_label = '';
2688

    
2689
  // add all classifications
2690
  $taxonomic_tree_options = array();
2691
  if ($add_none_option) {
2692
    $taxonomic_tree_options['NONE'] = ' '; // one Space character at beginning to force on top;
2693
  }
2694
  if ($taxonTrees) {
2695
    foreach ($taxonTrees as $tree) {
2696
      if(is_array($include_uuids) && count($include_uuids) > 0 && array_search($tree->uuid, $include_uuids) === FALSE){
2697
        continue;
2698
      }
2699
      if (!$default_classification_uuid || $default_classification_uuid != $tree->uuid) {
2700
        $taxonomic_tree_options[$tree->uuid] = $tree->titleCache;
2701
      } else {
2702
        $taxonomic_tree_options[$tree->uuid] = '  '; // two Space characters to force on top but below 'none' option , will be replaced below by titleCache
2703
        $default_classification_label = $tree->titleCache;
2704
      }
2705
    }
2706
  }
2707
  // oder alphabetically the space
2708
  asort($taxonomic_tree_options);
2709

    
2710
  // now set the labels for none
2711
  if ($add_none_option && count($taxonomic_tree_options) > 2) {
2712
    $taxonomic_tree_options['NONE'] =t('--- ALL ---');
2713
  } else {
2714
    unset($taxonomic_tree_options['NONE']);
2715
  }
2716

    
2717
  //   for default_classification
2718
  if (is_uuid($default_classification_uuid)) {
2719
    $taxonomic_tree_options[$default_classification_uuid] =
2720
      $default_classification_label ? $default_classification_label : '--- INVALID CHOICE ---'
2721
      . (count($taxonTrees) > 1 ? ' [' . t('DEFAULT CLASSIFICATION') . ']': '');
2722
  }
2723

    
2724
  return $taxonomic_tree_options;
2725
}
2726

    
2727
/**
2728
 * @todo Please document this function.
2729
 * @see http://drupal.org/node/1354
2730
 */
2731
function cdm_api_secref_cache_prefetch(&$secUuids) {
2732
  // Comment @WA: global variables should start with a single underscore
2733
  // followed by the module and another underscore.
2734
  global $_cdm_api_secref_cache;
2735
  if (!is_array($_cdm_api_secref_cache)) {
2736
    $_cdm_api_secref_cache = array();
2737
  }
2738
  $uniqueUuids = array_unique($secUuids);
2739
  $i = 0;
2740
  $param = '';
2741
  while ($i++ < count($uniqueUuids)) {
2742
    $param .= $secUuids[$i] . ',';
2743
    if (strlen($param) + 37 > 2000) {
2744
      _cdm_api_secref_cache_add($param);
2745
      $param = '';
2746
    }
2747
  }
2748
  if ($param) {
2749
    _cdm_api_secref_cache_add($param);
2750
  }
2751
}
2752

    
2753
/**
2754
 * @todo Please document this function.
2755
 * @see http://drupal.org/node/1354
2756
 */
2757
function cdm_api_secref_cache_get($secUuid) {
2758
  global $_cdm_api_secref_cache;
2759
  if (!is_array($_cdm_api_secref_cache)) {
2760
    $_cdm_api_secref_cache = array();
2761
  }
2762
  if (!array_key_exists($secUuid, $_cdm_api_secref_cache)) {
2763
    _cdm_api_secref_cache_add($secUuid);
2764
  }
2765
  return $_cdm_api_secref_cache[$secUuid];
2766
}
2767

    
2768
/**
2769
 * @todo Please document this function.
2770
 * @see http://drupal.org/node/1354
2771
 */
2772
function cdm_api_secref_cache_clear() {
2773
  global $_cdm_api_secref_cache;
2774
  $_cdm_api_secref_cache = array();
2775
}
2776

    
2777

    
2778
/**
2779
 * @todo Please document this function.
2780
 * @see http://drupal.org/node/1354
2781
 */
2782
function _cdm_api_secref_cache_add($secUuidsStr) {
2783
  global $_cdm_api_secref_cache;
2784
  $ref = cdm_ws_get(CDM_WS_REFERENCE, $secUuidsStr);
2785
  // Batch fetching not jet reimplemented thus:
2786
  /*
2787
  $assocRefSTOs = array(); if($refSTOs) { foreach($refSTOs as $ref){
2788
  $assocRefSTOs[$ref->uuid] = $ref; } $_cdm_api_secref_cache =
2789
  array_merge($_cdm_api_secref_cache, $assocRefSTOs); }
2790
  */
2791
  $_cdm_api_secref_cache[$ref->uuid] = $ref;
2792
}
2793

    
2794
/**
2795
 * Checks if the given uri starts with a cdm webservice url.
2796
 *
2797
 * Checks if the uri starts with the cdm webservice url stored in the
2798
 * Drupal variable 'cdm_webservice_url'.
2799
 * The 'cdm_webservice_url' can be set in the admins section of the portal.
2800
 *
2801
 * @param string $uri
2802
 *   The URI to test.
2803
 *
2804
 * @return bool
2805
 *   True if the uri starts with a cdm webservice url.
2806
 */
2807
function _is_cdm_ws_uri($uri) {
2808
  return str_beginsWith($uri, cdm_webservice_url('#EMPTY#'));
2809
}
2810

    
2811
/**
2812
 * @todo Please document this function.
2813
 * @see http://drupal.org/node/1354
2814
 */
2815
function queryString($elements) {
2816
  $query = '';
2817
  foreach ($elements as $key => $value) {
2818
    if (is_array($value)) {
2819
      foreach ($value as $v) {
2820
        $query .= (strlen($query) > 0 ? '&' : '') . $key . '=' . urlencode($v);
2821
      }
2822
    }
2823
    else {
2824
      $query .= (strlen($query) > 0 ? '&' : '') . $key . '=' . urlencode($value);
2825
    }
2826
  }
2827
  return $query;
2828
}
2829

    
2830
/**
2831
 * Compares the given CDM Term instances by the  representationL10n.
2832
 *
2833
 * Can also be used with TermDTOs. To be used in usort()
2834
 *
2835
 * @see http://php.net/manual/en/function.usort.php
2836
 *
2837
 * @param $term1
2838
 *   The first CDM Term instance
2839
 * @param $term2
2840
 *   The second CDM Term instance
2841
 * @return int
2842
 *   The result of the comparison
2843
 */
2844
function compare_terms_by_representationL10n($term1, $term2) {
2845

    
2846
  if (!isset($term1->representation_L10n)) {
2847
    $term1->representationL10n = '';
2848
  }
2849
  if (!isset($term2->representation_L10n)) {
2850
    $term2->representationL10n = '';
2851
  }
2852

    
2853
  return strcmp($term1->representation_L10n, $term2->representation_L10n);
2854
}
2855

    
2856
function compare_terms_by_order_index($term1, $term2) {
2857

    
2858

    
2859
  if (!isset($term1->orderIndex)) {
2860
    $a = 0;
2861
  } else {
2862
    $a = $term1->orderIndex;
2863
  }
2864
  if (!isset($term2->orderIndex)) {
2865
    $b = 0;
2866
  } else {
2867
    $b = $term2->orderIndex;
2868
  }
2869

    
2870
  if ($a == $b) {
2871
    return 0;
2872
  }
2873
  return ($a < $b) ? -1 : 1;
2874

    
2875
}
2876

    
2877

    
2878
/**
2879
 * Make a 'deep copy' of an array.
2880
 *
2881
 * Make a complete deep copy of an array replacing
2882
 * references with deep copies until a certain depth is reached
2883
 * ($maxdepth) whereupon references are copied as-is...
2884
 *
2885
 * @see http://us3.php.net/manual/en/ref.array.php
2886
 *
2887
 * @param array $array
2888
 * @param array $copy passed by reference
2889
 * @param int $maxdepth
2890
 * @param int $depth
2891
 */
2892
function array_deep_copy(&$array, &$copy, $maxdepth = 50, $depth = 0) {
2893
  if ($depth > $maxdepth) {
2894
    $copy = $array;
2895
    return;
2896
  }
2897
  if (!is_array($copy)) {
2898
    $copy = array();
2899
  }
2900
  foreach ($array as $k => &$v) {
2901
    if (is_array($v)) {
2902
      array_deep_copy($v, $copy[$k], $maxdepth, ++$depth);
2903
    }
2904
    else {
2905
      $copy[$k] = $v;
2906
    }
2907
  }
2908
}
2909

    
2910
/**
2911
 * Concatenated the uuids of the passed cdm entity with `,` as glue.
2912
 * The returned string is suitable for cdm webservices consuming UUIDList as
2913
 * parameter
2914
 *
2915
 * @param array $cdm_entities
2916
 *
2917
 * @return string
2918
 */
2919
function cdm_uuid_list_parameter_value(array $cdm_entities){
2920
  $uuids = [];
2921
  foreach ($cdm_entities as $entity){
2922
    if(isset($entity) && is_uuid($entity->uuid) ){
2923
      $uuids[] = $entity->uuid;
2924
    }
2925
  }
2926
  return  join(',', $uuids);
2927
}
2928

    
2929
/**
2930
 * Adds java script to create and enable a toggler for the cdm webservice debug block content.
2931
 *
2932
 */
2933
function _add_js_ws_debug() {
2934

    
2935
  $data_tables_js = '/js/DataTables-1.9.4/media/js/jquery.dataTables.min.js';
2936
  $colorbox_js = '/js/colorbox/jquery.colorbox-min.js';
2937
  if (variable_get('cdm_js_devel_mode', FALSE)) {
2938
    // use the developer versions of js libs
2939
    $data_tables_js = '/js/DataTables-1.9.4/media/js/jquery.dataTables.js';
2940
    $colorbox_js = '/js/colorbox/jquery.colorbox.js';
2941
  }
2942
  drupal_add_js(drupal_get_path('module', 'cdm_dataportal') . $data_tables_js,
2943
    array(
2944
      'type' => 'file',
2945
      'weight' => JS_LIBRARY,
2946
      'cache' => TRUE)
2947
    );
2948

    
2949
  drupal_add_js(drupal_get_path('module', 'cdm_dataportal') . $colorbox_js,
2950
    array(
2951
      'type' => 'file',
2952
      'weight' => JS_LIBRARY,
2953
      'cache' => TRUE)
2954
    );
2955
  drupal_add_css(drupal_get_path('module', 'cdm_dataportal') . '/js/colorbox/colorbox.css');
2956
  drupal_add_css(drupal_get_path('module', 'cdm_dataportal') . '/js/DataTables-1.9.4/media/css/cdm_debug_table.css');
2957

    
2958
  drupal_add_js(drupal_get_path('module', 'cdm_dataportal') . '/js/ws_debug_block.js',
2959
    array(
2960
      'type' => 'file',
2961
      'weight' => JS_LIBRARY,
2962
      'cache' => TRUE)
2963
    );
2964
  drupal_add_js(drupal_get_path('module', 'cdm_dataportal') . '/js/http-method-link.js',
2965
    array(
2966
    'type' => 'file',
2967
    'weight' => JS_LIBRARY,
2968
    'cache' => TRUE)
2969
    );
2970

    
2971
}
2972

    
2973
/**
2974
 * @todo Please document this function.
2975
 * @see http://drupal.org/node/1354
2976
 */
2977
function _no_classfication_uuid_message() {
2978
  if (!cdm_ws_get(CDM_WS_PORTAL_TAXONOMY)) {
2979
    return t('This DataPortal is not configured properly or the CDM-Server may be absent.') . ' Please check the ' . l(t('CDM web service URL'), 'admin/config/cdm_dataportal/settings/general') . t(', or contact the maintainer of this DataPortal.');
2980
  }
2981
  return t('This DataPortal is not configured properly.') . l(t('Please choose a valid classification'), 'admin/config/cdm_dataportal/settings/general') . t(', or contact the maintainer of this DataPortal.');
2982
}
2983

    
2984
/**
2985
 * Implementation of hook flush_caches
2986
 *
2987
 * Add custom cache tables to the list of cache tables that
2988
 * will be cleared by the Clear button on the Performance page or whenever
2989
 * drupal_flush_all_caches is invoked.
2990
 *
2991
 * @author W.Addink <waddink@eti.uva.nl>
2992
 *
2993
 * @return array
2994
 *   An array with custom cache tables to include.
2995
 */
2996
function cdm_api_flush_caches() {
2997
  return array('cache_cdm_ws');
2998
}
2999

    
3000
/**
3001
 * Logs if the drupal variable 'cdm_debug_mode' ist set true to drupal_debug.txt in the site's temp directory.
3002
 *
3003
 * @param $data
3004
 *   The variable to log to the drupal_debug.txt log file.
3005
 * @param $label
3006
 *   (optional) If set, a label to output before $data in the log file.
3007
 *
3008
 * @return
3009
 *   No return value if successful, FALSE if the log file could not be written
3010
 *   to.
3011
 *
3012
 * @see cdm_dataportal_init() where the log file is reset on each requests
3013
 * @see dd()
3014
 * @see http://drupal.org/node/314112
3015
 *
3016
 */
3017
function cdm_dd($data, $label = NULL) {
3018
  if(module_exists('devel') && variable_get('cdm_debug_mode', FALSE) && file_stream_wrapper_get_class('temporary') ){
3019
    return dd($data, $label);
3020
  }
3021
}
3022

    
(5-5/12)