Project

General

Profile

Download (92.7 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', $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
  $ws_base_uri = NULL;
1006
  switch ($cdm_type_simple) {
1007

    
1008
    case 'TaxonNode':
1009
    case 'TaxonNodeDto':
1010
      $ws_base_uri = CDM_WS_TAXONNODE;
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_REFERENCE;
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
/**
1092
 * Enter description here...
1093
 *
1094
 * @param string $resource_uri
1095
 * @param int $page_size
1096
 *   The maximum number of entities returned per page.
1097
 *   The default page size as configured in the cdm server
1098
 *   will be used if set to NULL
1099
 *   to return all entities in a single page).
1100
 * @param int $page_index
1101
 *   The number of the page to be returned, the first page has the
1102
 *   page_index = 0
1103
 * @param array $query
1104
 *   A array holding the HTTP request query parameters for the request
1105
 * @param string $method
1106
 *   The HTTP method to use, valid values are "GET" or "POST"
1107
 * @param bool $absolute_uri
1108
 *   TRUE when the URL should be treated as absolute URL.
1109
 *
1110
 * @return object
1111
 *   A CDM Pager object
1112
 *
1113
 */
1114
function cdm_ws_page($resource_uri, $page_size, $page_index, array $query = array(), $method = 'GET', $absolute_uri = FALSE) {
1115

    
1116
  $query['pageIndex'] = $page_index;
1117
  $query['pageSize'] = $page_size;
1118

    
1119
  $pager = cdm_ws_get($resource_uri, NULL, queryString($query), $method, $absolute_uri);
1120
  if(is_array($pager)){
1121
    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);
1122
    $records = $pager;
1123
    $pager = new stdClass();
1124
    $pager->records = $records;
1125
    $pager->count = count($records);
1126
    $pager->pageSize = $pager->count;
1127
    $pager->nextIndex = null;
1128
  }
1129
  return $pager;
1130
}
1131

    
1132

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

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

    
1170
  return cdm_ws_page(
1171
      'portal/' . cdm_ws_base_uri($cdm_entity_type),
1172
      $page_size,
1173
      $page_index,
1174
    $filter_parameters,
1175
    "GET"
1176
    );
1177
}
1178

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

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

    
1216

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

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

    
1254
/*
1255
function cdm_ws_taxonomy_compose_resourcePath($path = NULL){
1256
  $viewrank = _cdm_taxonomy_compose_viewrank();
1257
  return CDM_WS_PORTAL_TAXONOMY . '/' . ($viewrank ? $viewrank : '' ) . ($path
1258
  ? '/' . $path : '') ;
1259
}
1260
*/
1261

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

    
1275
  $view_uuid = get_current_classification_uuid();
1276
  $rank_uuid = NULL;
1277
  if (!$ignore_rank_limit) {
1278
    $rank_uuid = variable_get(TAXONTREE_RANKLIMIT, TAXONTREE_RANKLIMIT_DEFAULT);
1279
  }
1280

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

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

    
1323
    $response = NULL;
1324

    
1325
    // 1st try
1326
    $response = cdm_ws_get(cdm_compose_taxonomy_root_level_path($taxon_uuid), NULL, NULL, 'GET', TRUE);
1327

    
1328
    if ($response == NULL) {
1329
      // 2dn try by ignoring the rank limit
1330
      $response = cdm_ws_get(cdm_compose_taxonomy_root_level_path($taxon_uuid, TRUE), NULL, NULL, 'GET', TRUE);
1331
    }
1332

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

    
1357
  return $response;
1358
}
1359

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

    
1375
  $response = NULL;
1376
  if (is_uuid($rank_uuid)) {
1377
    $response = cdm_ws_get(CDM_WS_PORTAL_TAXONOMY_PATH_FROM_TO_RANK, array(
1378
      $view_uuid,
1379
      $taxon_uuid,
1380
      $rank_uuid,
1381
    ));
1382
  }
1383
  else {
1384
    $response = cdm_ws_get(CDM_WS_PORTAL_TAXONOMY_PATH_FROM, array(
1385
      $view_uuid,
1386
      $taxon_uuid,
1387
    ));
1388
  }
1389

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

    
1407
  return $response;
1408
}
1409

    
1410

    
1411
// =============================Terms and Vocabularies ========================================= //
1412

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

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

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

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

    
1482
  if($empty_option !== FALSE){
1483
    array_unshift ($options, "");
1484
  }
1485

    
1486
  return $options;
1487
}
1488

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

    
1510
  static $vocabularyOptions = array();
1511

    
1512
  if (!isset($vocabularyOptions[$vocabulary_uuid])) {
1513
    $terms = cdm_ws_fetch_all('termVocabulary/' . $vocabulary_uuid . '/terms',
1514
      array(
1515
        'orderBy' => $order_by
1516
      )
1517
    );
1518

    
1519
    // apply the include filter
1520
    if($include_filter != null){
1521
      $included_terms = array();
1522

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

    
1536
      $terms = $included_terms;
1537
    }
1538

    
1539
    // make options list
1540
    $vocabularyOptions[$vocabulary_uuid] = cdm_terms_as_options($terms, $term_label_callback, $empty_option);
1541
  }
1542

    
1543
  $options = $vocabularyOptions[$vocabulary_uuid];
1544

    
1545
  return $options;
1546
}
1547

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

    
1568
  $options = cdm_vocabulary_as_option($vocabulary_uuid, null, null, $include_filter);
1569
  $defaults = array();
1570
  foreach ($options as $uuid => $value){
1571
    $defaults[$uuid] = $uuid;
1572
  }
1573

    
1574
  return $defaults;
1575
}
1576

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

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

    
1653
  if(isset($filter_terms) && is_array($filter_terms)) {
1654
    foreach ($filter_terms as $filter_term) {
1655
      $options[join(',', $filter_term->uuids)] = $filter_term->label;
1656
    }
1657
  }
1658

    
1659
  if(is_string($none_option_label)){
1660
    $options = array_merge(array('NULL' => $none_option_label), $options);
1661
  }
1662

    
1663
  if($with_empty_option !== FALSE){
1664
    array_unshift ($options, "");
1665
  }
1666

    
1667

    
1668
  return $options;
1669
}
1670

    
1671

    
1672

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

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

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

    
1723
  if($is_inverse_relation) {
1724
    if (isset($term->inverseRepresentation_L10n_abbreviatedLabel) && $term->inverseRepresentation_L10n_abbreviatedLabel) {
1725
      $abbr_label = $term->inverseResentation_L10n_abbreviatedLabel;
1726
    } else {
1727
      $abbr_label = $term->inverseRepresentation_L10n;
1728
    }
1729
  } else {
1730
    if (isset($term->representation_L10n_abbreviatedLabel) && $term->representation_L10n_abbreviatedLabel) {
1731
      $abbr_label = $term->representation_L10n_abbreviatedLabel;
1732
    } else {
1733
      $abbr_label = $term->representation_L10n;
1734
    }
1735
  }
1736
  return $abbr_label;
1737
}
1738

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

    
1752
  if($is_inverse_relation) {
1753
    if (isset($term->inverseSymbol) && $term->inverseSymbol) {
1754
      $symbol = $term->inverseSymbol;
1755
    } else if (isset($term->inverseRepresentation_L10n_abbreviatedLabel) && $term->inverseRepresentation_L10n_abbreviatedLabel) {
1756
      $symbol = $term->inverseResentation_L10n_abbreviatedLabel;
1757
    } else {
1758
      $symbol = $term->inverseRepresentation_L10n;
1759
    }
1760
  } else {
1761
    if (isset($term->symbol) && $term->symbol) {
1762
      $symbol = $term->symbol;
1763
    } else if (isset($term->representation_L10n_abbreviatedLabel) && $term->representation_L10n_abbreviatedLabel) {
1764
      $symbol = $term->representation_L10n_abbreviatedLabel;
1765
    } else {
1766
      $symbol = $term->representation_L10n;
1767
    }
1768
  }
1769
  return $symbol;
1770
}
1771

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

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

    
1810

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

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

    
1835
  $description_elements = cdm_ws_fetch_all(CDM_WS_DESCRIPTIONELEMENT_BY_TAXON,
1836
      array(
1837
      'taxon' => $taxon_uuid,
1838
      'features' => cdm_featureTree_elements_toString($feature_tree->root, ',', 'uuid', $exclude_uuids)
1839
      ),
1840
      'POST'
1841
  );
1842

    
1843
  // Combine all descriptions into one feature tree.
1844
  $merged_nodes = _mergeFeatureTreeDescriptions($feature_tree->root->childNodes, $description_elements);
1845
  $feature_tree->root->childNodes = $merged_nodes;
1846

    
1847
  return $feature_tree;
1848
}
1849

    
1850
/**
1851
 * Returns a filtered a list of annotations for the cdm entity given as parameter $cdm_entity.
1852
 * If the annotations are not yet already loded with the cdm entity the cdm REST service will
1853
 * be requested for the annotations.
1854
 *
1855
 * @param string $cdm_entity
1856
 *   An annotatable cdm entity.
1857
 * @param array $include_types
1858
 *   If an array of annotation type uuids is supplied by this parameter the
1859
 *   list of annotations is resticted to those which belong to this type.
1860
 *
1861
 * @return array
1862
 *   An array of Annotation objects or an empty array.
1863
 */
1864
function cdm_ws_fetch_annotations(&$cdm_entity, $include_types = FALSE) {
1865

    
1866
  if(!isset($cdm_entity->annotations)){
1867
    $annotation_url = cdm_compose_annotations_uri($cdm_entity);
1868
    $cdm_entity->annotations = cdm_ws_fetch_all($annotation_url, array(), 'GET', TRUE);
1869
  }
1870

    
1871
  $annotations = array();
1872
  foreach ($cdm_entity->annotations as $annotation) {
1873
    if ($include_types) {
1874
      if (
1875
        ( isset($annotation->annotationType->uuid) && in_array($annotation->annotationType->uuid, $include_types, TRUE) )
1876
        || ($annotation->annotationType === NULL && in_array('NULL_VALUE', $include_types, TRUE))
1877
      ) {
1878
        $annotations[] = $annotation;
1879
      }
1880
    }
1881
    else {
1882
      $annotations[] = $annotation;
1883
    }
1884
  }
1885
  return $annotations;
1886

    
1887
}
1888

    
1889
/**
1890
 * Provides the list of visible annotations for the $cdm_entity.
1891
 *
1892
 * @param $cdm_entity
1893
 *     The annotatable CDM entity
1894
 *
1895
 * @return array of the annotations which are visible according to the settings as stored in ANNOTATION_TYPES_VISIBLE
1896
 */
1897
function cdm_fetch_visible_annotations($cdm_entity){
1898

    
1899
  static $annotations_types_filter = null;
1900
  if(!$annotations_types_filter) {
1901
    $annotations_types_filter = unserialize(EXTENSION_TYPES_VISIBLE_DEFAULT);
1902
  }
1903
  return cdm_ws_fetch_annotations($cdm_entity, variable_get(ANNOTATION_TYPES_VISIBLE, $annotations_types_filter));
1904
}
1905

    
1906
/**
1907
 * Loads the annotations from the REST service an adds them as field to the given $annotatable_entity.
1908
 *
1909
 * NOTE: The annotations are not filtered by the ANNOTATION_TYPES_VISIBLE settings since this method is meant to act
1910
 * like the annotations have been fetched in the ORM-framework in the service.
1911
 *
1912
 * @param object $annotatable_entity
1913
 *   The CDM AnnotatableEntity to load annotations for
1914
 */
1915
function cdm_load_annotations(&$annotatable_entity) {
1916
  if (isset($annotatable_entity) && !isset($annotatable_entity->annotations)) {
1917
    $annotations = cdm_ws_fetch_annotations($annotatable_entity);
1918
    if (is_array($annotations)) {
1919
      $annotatable_entity->annotations = $annotations;
1920
    }
1921
  }
1922
}
1923

    
1924
function cdm_load_tagged_full_title($taxon_name){
1925
  if(isset($taxon_name) && !isset($taxon_name->taggedFullTitle)){
1926
    $tagged_full_title = cdm_ws_get(CDM_WS_NAME, array($taxon_name->uuid, 'taggedFullTitle'));
1927
    if(is_array($tagged_full_title)){
1928
      $taxon_name->taggedFullTitle = $tagged_full_title;
1929

    
1930
    }
1931
  }
1932
}
1933

    
1934
/**
1935
 * Extends the $cdm_entity object by the field if it is not already existing.
1936
 *
1937
 * This function can only be used for fields with 1 to many relations.
1938
  *
1939
 * @param $cdm_base_type
1940
 * @param $field_name
1941
 * @param $cdm_entity
1942
 */
1943
function cdm_lazyload_array_field($cdm_base_type, $field_name, &$cdm_entity)
1944
{
1945
  if (!isset($cdm_entity->$field_name)) {
1946
    $items = cdm_ws_fetch_all('portal/' . $cdm_base_type . '/' . $cdm_entity->uuid . '/' . $field_name);
1947
    $cdm_entity->$field_name = $items;
1948
  }
1949
}
1950

    
1951

    
1952
/**
1953
 * Get a NomenclaturalReference string.
1954
 *
1955
 * Returns the NomenclaturalReference string with correctly placed
1956
 * microreference (= reference detail) e.g.
1957
 * in Phytotaxa 43: 1-48. 2012.
1958
 *
1959
 * @param string $referenceUuid
1960
 *   UUID of the reference.
1961
 * @param string $microreference
1962
 *   Reference detail.
1963
 *
1964
 * @return string
1965
 *   a NomenclaturalReference.
1966
 */
1967
function cdm_ws_getNomenclaturalReference($referenceUuid, $microreference) {
1968

    
1969
  // TODO the below statement avoids error boxes due to #4644 remove it once this ticket is solved
1970
  if(is_array($microreference) || is_object($microreference)) {
1971
    return '';
1972
  }
1973

    
1974
  $obj = cdm_ws_get(CDM_WS_NOMENCLATURAL_REFERENCE_CITATION, array(
1975
    $referenceUuid,
1976
  ), "microReference=" . urlencode($microreference));
1977

    
1978
  if ($obj) {
1979
    return $obj->String;
1980
  }
1981
  else {
1982
    return NULL;
1983
  }
1984
}
1985

    
1986
/**
1987
 * finds and returns the FeatureNode denoted by the given $feature_uuid
1988
 *
1989
 * @param $feature_tree_nodes
1990
 *    The nodes contained in CDM FeatureTree entitiy: $feature->root->childNodes
1991
 * @param $feature_uuid
1992
 *    The UUID of the Feature
1993
 * @return object
1994
 *    the FeatureNode or null
1995
 */
1996
function &cdm_feature_tree_find_node($feature_tree_nodes, $feature_uuid){
1997

    
1998
  // 1. scan this level
1999
  foreach ($feature_tree_nodes as $node){
2000
    if($node->term->uuid == $feature_uuid){
2001
      return $node;
2002
    }
2003
  }
2004

    
2005
  // 2. descend into childen
2006
  foreach ($feature_tree_nodes as $node){
2007
    if(is_array($node->childNodes)){
2008
      $node = cdm_feature_tree_find_node($node->childNodes, $feature_uuid);
2009
      if($node) {
2010
        return $node;
2011
      }
2012
    }
2013
  }
2014
  $null_var = null; // kludgy workaround to avoid "PHP Notice: Only variable references should be returned by reference"
2015
  return $null_var;
2016
}
2017

    
2018
/**
2019
 * Merges the given featureNodes structure with the descriptionElements.
2020
 *
2021
 * This method is used in preparation for rendering the descriptionElements.
2022
 * The descriptionElements which belong to a specific feature node are appended
2023
 * to a the feature node by creating a new field:
2024
 *  - descriptionElements: the CDM DescriptionElements which belong to this feature
2025
 * The descriptionElements will be cleared in advance in order to allow reusing the
2026
 * same feature tree without the risk of mixing sets of description elements.
2027
 *
2028
 * which originally is not existing in the cdm.
2029
 *
2030
 *
2031
 *
2032
 * @param array $featureNodes
2033
 *    An array of cdm FeatureNodes which may be hierarchical since feature nodes
2034
 *    may have children.
2035
 * @param array $descriptionElements
2036
 *    An flat array of cdm DescriptionElements
2037
 * @return array
2038
 *    The $featureNodes structure enriched with the according $descriptionElements
2039
 */
2040
function _mergeFeatureTreeDescriptions($featureNodes, $descriptionElements) {
2041

    
2042
  foreach ($featureNodes as &$feature_node) {
2043
    // since the $featureNodes array is reused for each description
2044
    // it is necessary to clear the custom node fields in advance
2045
    if(isset($feature_node->descriptionElements)){
2046
      unset($feature_node->descriptionElements);
2047
    }
2048

    
2049
    // Append corresponding elements to an additional node field:
2050
    // $node->descriptionElements.
2051
    foreach ($descriptionElements as $element) {
2052
      if ($element->feature->uuid == $feature_node->term->uuid) {
2053
        if (!isset($feature_node->descriptionElements)) {
2054
          $feature_node->descriptionElements = array();
2055
        }
2056
        $feature_node->descriptionElements[] = $element;
2057
      }
2058
    }
2059

    
2060
    // Recurse into node children.
2061
    if (isset($feature_node->childNodes[0])) {
2062
      $mergedChildNodes = _mergeFeatureTreeDescriptions($feature_node->childNodes, $descriptionElements);
2063
      $feature_node->childNodes = $mergedChildNodes;
2064
    }
2065

    
2066
    if(!isset($feature_node->descriptionElements) && !isset($feature_node->childNodes[0])){
2067
      unset($feature_node);
2068
    }
2069

    
2070
  }
2071

    
2072
  return $featureNodes;
2073
}
2074

    
2075
/**
2076
 * Sends a GET or POST request to a CDM RESTService and returns a de-serialized object.
2077
 *
2078
 * The response from the HTTP GET request is returned as object.
2079
 * The response objects coming from the webservice configured in the
2080
 * 'cdm_webservice_url' variable are being cached in a level 1 (L1) and / or
2081
 *  in a level 2 (L2) cache.
2082
 *
2083
 * Since the L1 cache is implemented as static variable of the cdm_ws_get()
2084
 * function, this cache persists only per each single page execution.
2085
 * Any object coming from the webservice is stored into it by default.
2086
 * In contrast to this default caching mechanism the L2 cache only is used if
2087
 * the 'cdm_webservice_cache' variable is set to TRUE,
2088
 * which can be set using the modules administrative settings section.
2089
 * Objects stored in this L2 cache are serialized and stored
2090
 * using the drupal cache in the '{prefix}cache_cdm_ws' cache table. So the
2091
 * objects that are stored in the database will persist as
2092
 * long as the drupal cache is not being cleared and are available across
2093
 * multiple script executions.
2094
 *
2095
 * @param string $uri
2096
 *   URL to the webservice.
2097
 * @param array $pathParameters
2098
 *   An array of path parameters.
2099
 * @param string $query_string
2100
 *   A query_string string to be appended to the URL.
2101
 * @param string $method
2102
 *   The HTTP method to use, valid values are "GET" or "POST";
2103
 * @param bool $absoluteURI
2104
 *   TRUE when the URL should be treated as absolute URL.
2105
 *
2106
 * @return object| array
2107
 *   The de-serialized webservice response object.
2108
 */
2109
function cdm_ws_get($uri, $pathParameters = array(), $query_string = NULL, $method = "GET", $absoluteURI = FALSE) {
2110

    
2111
  static $cacheL1 = array();
2112

    
2113
  $data = NULL;
2114
  // store query_string string in $data and clear the query_string, $data will be set as HTTP request body
2115
  if($method == 'POST'){
2116
    $data = $query_string;
2117
    $query_string = NULL;
2118
  }
2119

    
2120
  // Transform the given uri path or pattern into a proper webservice uri.
2121
  if (!$absoluteURI) {
2122
    $uri = cdm_compose_ws_url($uri, $pathParameters, $query_string);
2123
  } else {
2124
    if($query_string){
2125
      $uri = append_query_parameters($uri, $query_string);
2126
    }
2127
  }
2128
  cdm_ws_apply_classification_subtree_filter($uri);
2129

    
2130
  // read request parameter 'cacheL2_refresh'
2131
  // which allows refreshing the level 2 cache
2132
  $do_cacheL2_refresh = isset($_REQUEST['cacheL2_refresh']) && $_REQUEST['cacheL2_refresh'] == 1;
2133

    
2134
  $is_cdm_ws_uri = _is_cdm_ws_uri($uri);
2135
  $use_cacheL2 = variable_get('cdm_webservice_cache', 1);
2136

    
2137
  if($method == 'GET'){
2138
    $cache_key = $uri;
2139
  } else {
2140
    // sha1 creates longer hashes and thus will cause fewer collisions than md5.
2141
    // crc32 is faster but creates much shorter hashes
2142
    $cache_key = $uri . '[' . $method . ':' . sha1($data) .']';
2143
  }
2144

    
2145
  if (array_key_exists($cache_key, $cacheL1)) {
2146
    $cacheL1_obj = $cacheL1[$uri];
2147
  }
2148

    
2149
  $set_cacheL1 = FALSE;
2150
  if ($is_cdm_ws_uri && !isset($cacheL1_obj)) {
2151
    $set_cacheL1 = TRUE;
2152
  }
2153

    
2154
  // Only cache cdm webservice URIs.
2155
  $set_cacheL2 = $use_cacheL2 && $is_cdm_ws_uri && $set_cacheL1;
2156
  $cacheL2_entry = FALSE;
2157

    
2158
  if ($use_cacheL2 && !$do_cacheL2_refresh) {
2159
    // Try to get object from cacheL2.
2160
    $cacheL2_entry = cache_get($cache_key, 'cache_cdm_ws');
2161
  }
2162

    
2163
  if (isset($cacheL1_obj)) {
2164
    //
2165
    // The object has been found in the L1 cache.
2166
    //
2167
    $obj = $cacheL1_obj;
2168
    if (cdm_debug_block_visible()) {
2169
      cdm_ws_debug_add($uri, $method, $data, 0, 0, NULL, 'cacheL1');
2170
    }
2171
  }
2172
  elseif ($cacheL2_entry) {
2173
    //
2174
    // The object has been found in the L2 cache.
2175
    //
2176
    $duration_parse_start = microtime(TRUE);
2177
    $obj = unserialize($cacheL2_entry->data);
2178
    $duration_parse = microtime(TRUE) - $duration_parse_start;
2179

    
2180
    if (cdm_debug_block_visible()) {
2181
      cdm_ws_debug_add($uri, $method, $data, 0, $duration_parse, NULL, 'cacheL2');
2182
    }
2183
  }
2184
  else {
2185
    //
2186
    // Get the object from the webservice and cache it.
2187
    //
2188
    $duration_fetch_start = microtime(TRUE);
2189
    // Request data from webservice JSON or XML.
2190
    $response = cdm_http_request($uri, $method, $data);
2191
    $response_body = NULL;
2192
    if (isset($response->data)) {
2193
      $response_body = $response->data;
2194
    }
2195
    $duration_fetch = microtime(TRUE) - $duration_fetch_start;
2196
    $duration_parse_start = microtime(TRUE);
2197

    
2198
    // Parse data and create object.
2199
    $obj = cdm_load_obj($response_body);
2200

    
2201
    if(isset($obj->servlet) && isset($obj->status) && is_numeric($obj->status)){
2202
      // this is json error message returned by jetty #8914
2203
      // wee need to replace it by null to avoid breaking existing assumptions in the code here
2204
      // this is also related to #2711
2205
      $obj = null;
2206
    }
2207

    
2208
    $duration_parse = microtime(TRUE) - $duration_parse_start;
2209

    
2210
    if (cdm_debug_block_visible()) {
2211
      if ($obj || $response_body == "[]") {
2212
        $status = 'valid';
2213
      }
2214
      else {
2215
        $status = 'invalid';
2216
      }
2217
      cdm_ws_debug_add($uri, $method, $data, $duration_fetch, $duration_parse, strlen($response_body), $status);
2218
    }
2219
    if ($set_cacheL2) {
2220
      // Store the object in cache L2.
2221
      // Comment @WA perhaps better if Drupal serializedatas here? Then the
2222
      // flag serialized is set properly in the cache table.
2223
      cache_set($cache_key, serialize($obj), 'cache_cdm_ws', CACHE_TEMPORARY);
2224
    }
2225
  }
2226
  if ($obj) {
2227
    // Store the object in cache L1.
2228
    if ($set_cacheL1) {
2229
      $cacheL1[$cache_key] = $obj;
2230
    }
2231
  }
2232
  return $obj;
2233
}
2234

    
2235
function cdm_ws_apply_classification_subtree_filter(&$uri){
2236

    
2237
  $classification_subtree_filter_patterns = &drupal_static('classification_subtree_filter_patterns', array(
2238
    "#/classification/[0-9a-f\-]{36}/childNodes#",
2239
    /* covered by above pattern:
2240
    "#/classification/[0-9a-f\-]{36}/childNodesAt/[0-9a-f\-]{36}#",
2241
    '#/classification/[0-9a-f\-]{36}/childNodesOf/[0-9a-f\-]{36}#',
2242
    */
2243
    "#/portal/classification/[0-9a-f\-]{36}/childNodes#",
2244
    /* covered by above pattern:
2245
    "#/portal/classification/[0-9a-f\-]{36}/childNodesAt/[0-9a-f\-]{36}#",
2246
    '#/portal/classification/[0-9a-f\-]{36}/childNodesOf/[0-9a-f\-]{36}#',
2247
    */
2248
    '#/portal/classification/[0-9a-f\-]{36}/pathFrom/[0-9a-f\-]{36}#',
2249
    "#/portal/taxon/search#",
2250
    "#/portal/taxon/find#",
2251
    /* covered by above pattern:
2252
    "#/portal/taxon/findByDescriptionElementFullText#",
2253
    "#/portal/taxon/findByFullText#",
2254
    "#/portal/taxon/findByEverythingFullText#",
2255
    "#/portal/taxon/findByIdentifier#",
2256
    "#/portal/taxon/findByMarker#",
2257
    "#/portal/taxon/findByMarker#",
2258
    "#/portal/taxon/findByMarker#",
2259
    */
2260
    "#/portal/taxon/[0-9a-f\-]{36}#"
2261
    /* covered by above pattern:
2262
    "#/portal/taxon/[0-9a-f\-]{36}/taxonNodes#",
2263
    */
2264
  ));
2265

    
2266
  $sub_tree_filter_uuid_value = variable_get(CDM_SUB_TREE_FILTER_UUID, FALSE);
2267
  if(is_uuid($sub_tree_filter_uuid_value)){
2268
    foreach($classification_subtree_filter_patterns as $preg_pattern){
2269
      if(preg_match($preg_pattern, $uri)){
2270
        // no need to take care for uri fragments with ws uris!
2271
        if(strpos( $uri, '?')){
2272
          $uri .= '&subtree=' . $sub_tree_filter_uuid_value;
2273
        } else {
2274
          $uri .= '?subtree='. $sub_tree_filter_uuid_value;
2275
        }
2276
        break;
2277
      }
2278
    }
2279
  }
2280

    
2281
}
2282
/**
2283
 * Processes and stores the given information in $_SESSION['cdm']['ws_debug'] as table row.
2284
 *
2285
 * The cdm_ws_debug block will display the debug information.
2286
 *
2287
 * @param $uri
2288
 *    The CDM REST URI to which the request has been send
2289
 * @param string $method
2290
 *    The HTTP request method, either 'GET' or 'POST'
2291
 * @param string $post_data
2292
 *    The datastring send with a post request
2293
 * @param $duration_fetch
2294
 *    The time in seconds it took to fetch the data from the web service
2295
 * @param $duration_parse
2296
 *    Time in seconds which was needed to parse the json response
2297
 * @param $datasize
2298
 *    Size of the data received from the server
2299
 * @param $status
2300
 *    A status string, possible values are: 'valid', 'invalid', 'cacheL1', 'cacheL2'
2301
 * @return bool
2302
 *    TRUE if adding the debug information was successful
2303
 */
2304
function cdm_ws_debug_add($uri, $method, $post_data, $duration_fetch, $duration_parse, $datasize, $status) {
2305

    
2306
  static $initial_time = NULL;
2307
  if(!$initial_time) {
2308
    $initial_time = microtime(TRUE);
2309
  }
2310
  $time = microtime(TRUE) - $initial_time;
2311

    
2312
  // Decompose uri into path and query element.
2313
  $uri_parts = explode("?", $uri);
2314
  $query = array();
2315
  if (count($uri_parts) == 2) {
2316
    $path = $uri_parts[0];
2317
  }
2318
  else {
2319
    $path = $uri;
2320
  }
2321

    
2322
  if(strpos($uri, '?') > 0){
2323
    $json_uri = str_replace('?', '.json?', $uri);
2324
    $xml_uri = str_replace('?', '.xml?', $uri);
2325
  } else {
2326
    $json_uri = $uri . '.json';
2327
    $xml_uri = $json_uri . '.xml';
2328
  }
2329

    
2330
  // data links to make data accessible as json and xml
2331
  $data_links = '';
2332
  if (_is_cdm_ws_uri($path)) {
2333

    
2334
    // see ./js/http-method-link.js
2335

    
2336
    if($method == 'GET'){
2337
      $data_links .= '<a href="' . $xml_uri . '" target="data">xml</a>-';
2338
      $data_links .= '<a href="' . url('cdm_api/proxy/' . urlencode($xml_uri)) . '" target="data">proxied</a>';
2339
      $data_links .= '<br/>';
2340
      $data_links .= '<a href="' . $json_uri . '" target="data">json</a>-';
2341
      $data_links .= '<a href="' . url('cdm_api/proxy/' . urlencode($json_uri)) . '" target="data">proxied</a>';
2342
    } else {
2343
      $js_link_activation = 'class="http-' . $method . '-link" data-cdm-http-post="' . $post_data . '" type="application/x-www-form-urlencoded"';
2344
      $data_links .= '<a ' . $js_link_activation . ' href="' . url('cdm_api/proxy/' . urlencode($xml_uri)) . '" target="data">xml-proxied</a>';
2345
      $data_links .= '<br/>';
2346
      $data_links .= '<a ' . $js_link_activation . ' href="' . url('cdm_api/proxy/' . urlencode($json_uri)) . '" target="data">json-proxied</a>';
2347
    }
2348
  }
2349
  else {
2350
    $data_links .= '<a href="' . $uri . '" target="data">open</a>';
2351
  }
2352

    
2353
  //
2354
  $data = array(
2355
      'ws_uri' => $uri,
2356
      'method' => $method,
2357
      'post_data' => $post_data,
2358
      'time' => sprintf('%3.3f', $time),
2359
      'fetch_seconds' => sprintf('%3.3f', $duration_fetch),
2360
      'parse_seconds' => sprintf('%3.3f', $duration_parse),
2361
      'size_kb' => sprintf('%3.1f', ($datasize / 1024)) ,
2362
      'status' => $status,
2363
      'data_links' => $data_links
2364
  );
2365
  if (!isset($_SESSION['cdm']['ws_debug'])) {
2366
    $_SESSION['cdm']['ws_debug'] = array();
2367
  }
2368
  $_SESSION['cdm']['ws_debug'][] = serialize($data);
2369

    
2370
  // Mark this page as being uncacheable.
2371
  // taken over from drupal_get_messages() but it is unsure if we really need this here
2372
  drupal_page_is_cacheable(FALSE);
2373

    
2374
  // Messages not set when DB connection fails.
2375
  return isset($_SESSION['cdm']['ws_debug']) ? $_SESSION['cdm']['ws_debug'] : NULL;
2376
}
2377

    
2378
/**
2379
 * helper function to dtermine if the cdm_debug_block should be displayed or not
2380
 * the visibility depends on whether
2381
 *  - the block is enabled
2382
 *  - the visibility restrictions in the block settings are satisfied
2383
 */
2384
function cdm_debug_block_visible() {
2385
  static $is_visible = null;
2386

    
2387
  if($is_visible === null){
2388
      $block = block_load('cdm_api', 'cdm_ws_debug');
2389
      $is_visible = isset($block->status) && $block->status == 1;
2390
      if($is_visible){
2391
        $blocks = array($block);
2392
        // Checks the page, user role, and user-specific visibilty settings.
2393
        block_block_list_alter($blocks);
2394
        $is_visible = count($blocks) > 0;
2395
      }
2396
  }
2397
  return $is_visible;
2398
}
2399

    
2400
/**
2401
 * @todo Please document this function.
2402
 * @see http://drupal.org/node/1354
2403
 */
2404
function cdm_load_obj($response_body) {
2405
  $obj = json_decode($response_body);
2406

    
2407
  if (!(is_object($obj) || is_array($obj))) {
2408
    ob_start();
2409
    $obj_dump = ob_get_contents();
2410
    ob_clean();
2411
    return FALSE;
2412
  }
2413

    
2414
  return $obj;
2415
}
2416

    
2417
/**
2418
 * Do a http request to a CDM RESTful web service.
2419
 *
2420
 * @param string $uri
2421
 *   The webservice url.
2422
 * @param string $method
2423
 *   The HTTP method to use, valid values are "GET" or "POST"; defaults to
2424
 *   "GET" even if NULL, FALSE or any invalid value is supplied.
2425
 * @param $data: A string containing the request body, formatted as
2426
 *     'param=value&param=value&...'. Defaults to NULL.
2427
 *
2428
 * @return object
2429
 *   The object as returned by drupal_http_request():
2430
 *   An object that can have one or more of the following components:
2431
 *   - request: A string containing the request body that was sent.
2432
 *   - code: An integer containing the response status code, or the error code
2433
 *     if an error occurred.
2434
 *   - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
2435
 *   - status_message: The status message from the response, if a response was
2436
 *     received.
2437
 *   - redirect_code: If redirected, an integer containing the initial response
2438
 *     status code.
2439
 *   - redirect_url: If redirected, a string containing the URL of the redirect
2440
 *     target.
2441
 *   - error: If an error occurred, the error message. Otherwise not set.
2442
 *   - headers: An array containing the response headers as name/value pairs.
2443
 *     HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
2444
 *     easy access the array keys are returned in lower case.
2445
 *   - data: A string containing the response body that was received.
2446
 */
2447
function cdm_http_request($uri, $method = "GET", $data = NULL) {
2448
  static $acceptLanguage = NULL;
2449
  $header = array();
2450
  
2451
  if(!$acceptLanguage && module_exists('i18n')){
2452
    $acceptLanguage = i18n_language_content()->language;
2453
  }
2454

    
2455
  if (!$acceptLanguage) {
2456
    if (function_exists('apache_request_headers')) {
2457
      $headers = apache_request_headers();
2458
      if (isset($headers['Accept-Language'])) {
2459
        $acceptLanguage = $headers['Accept-Language'];
2460
      }
2461
    }
2462
  }
2463

    
2464
  if ($method != "GET" && $method != "POST") {
2465
    drupal_set_message('cdm_api.module#cdm_http_request() : unsupported HTTP request method ', 'error');
2466
  }
2467

    
2468
  if (_is_cdm_ws_uri($uri)) {
2469
    $header['Accept'] = 'application/json';
2470
    $header['Accept-Language'] = $acceptLanguage;
2471
    $header['Accept-Charset'] = 'UTF-8';
2472
  }
2473

    
2474
  if($method == "POST") {
2475
    // content type is application/x-www-form-urlencoded, so the request body uses the same format as the query string
2476
    $header['Content-Type'] = 'application/x-www-form-urlencoded';
2477
  }
2478

    
2479
  $context_resource = null;
2480
  if(!variable_get('cdm_webservice_url_ssl_verify', 1)){
2481
    $context_resource = stream_context_create(array('ssl' => array('verify_peer' => FALSE, 'verify_peer_name' => FALSE)));
2482
  }
2483
  cdm_dd($uri);
2484
  return drupal_http_request($uri, array(
2485
      'headers' => $header,
2486
      'method' => $method,
2487
      'data' => $data,
2488
      'timeout' => CDM_HTTP_REQUEST_TIMEOUT,
2489
      'context' => $context_resource
2490
      )
2491
   );
2492
}
2493

    
2494
/**
2495
 * Concatenates recursively the fields of all features contained in the given
2496
 * CDM FeatureTree root node.
2497
 *
2498
 * @param $rootNode
2499
 *     A CDM FeatureTree node
2500
 * @param
2501
 *     The character to be used as glue for concatenation, default is ', '
2502
 * @param $field_name
2503
 *     The field name of the CDM Features
2504
 * @param $excludes
2505
 *     Allows defining a set of values to be excluded. This refers to the values
2506
 *     in the field denoted by the $field_name parameter
2507
 *
2508
 */
2509
function cdm_featureTree_elements_toString($root_node, $separator = ', ', $field_name = 'representation_L10n', $excludes = array()) {
2510
  $out = '';
2511

    
2512
  $pre_child_separator = $separator;
2513
  $post_child_separator = '';
2514

    
2515
  foreach ($root_node->childNodes as $feature_node) {
2516
    $out .= ($out ? $separator : '');
2517
    if(!in_array($feature_node->term->$field_name, $excludes)) {
2518
      $out .= $feature_node->term->$field_name;
2519
      if (is_array($feature_node->childNodes) && count($feature_node->childNodes) > 0) {
2520
        $childlabels = cdm_featureTree_elements_toString($feature_node, $separator, $field_name);
2521
        if (strlen($childlabels)) {
2522
            $out .=  $pre_child_separator . $childlabels . $post_child_separator;
2523
        }
2524
      }
2525
    }
2526
  }
2527
  return $out;
2528
}
2529

    
2530
/**
2531
 * Create a one-dimensional form options array.
2532
 *
2533
 * Creates an array of all features in the feature tree of feature nodes,
2534
 * the node labels are indented by $node_char and $childIndent depending on the
2535
 * hierachy level.
2536
 *
2537
 * @param - $rootNode
2538
 * @param - $node_char
2539
 * @param - $childIndentStr
2540
 * @param - $childIndent
2541
 *   ONLY USED INTERNALLY!
2542
 *
2543
 * @return array
2544
 *   A one dimensional Drupal form options array.
2545
 */
2546
function _featureTree_nodes_as_feature_options($rootNode, $node_char = "&#9500;&#9472; ", $childIndentStr = '&nbsp;', $childIndent = '') {
2547
  $options = array();
2548
  foreach ($rootNode->childNodes as $featureNode) {
2549
    $indent_prefix = '';
2550
    if ($childIndent) {
2551
      $indent_prefix = $childIndent . $node_char . " ";
2552
    }
2553
    $options[$featureNode->term->uuid] = $indent_prefix . $featureNode->term->representation_L10n;
2554
    if (isset($featureNode->childNodes) && is_array($featureNode->childNodes)) {
2555
      // Foreach ($featureNode->childNodes as $childNode){
2556
      $childList = _featureTree_nodes_as_feature_options($featureNode, $node_char, $childIndentStr, $childIndent . $childIndentStr);
2557
      $options = array_merge_recursive($options, $childList);
2558
      // }
2559
    }
2560
  }
2561
  return $options;
2562
}
2563

    
2564
/**
2565
 * Returns an array with all available FeatureTrees and the representations of the selected
2566
 * FeatureTree as a detail view.
2567
 *
2568
 * @param boolean $add_default_feature_free
2569
 * @param boolean $show_weight
2570
 *     Show the weight which will be applied to the according feature block
2571
 * @return array
2572
 *  associative array with following keys:
2573
 *  -options: Returns an array with all available Feature Trees
2574
 *  -treeRepresentations: Returns representations of the selected Feature Tree as a detail view
2575
 *
2576
 */
2577
function cdm_get_featureTrees_as_options($add_default_feature_free = FALSE, $show_weight = FALSE) {
2578

    
2579
  $options = array();
2580
  $tree_representations = array();
2581
  $feature_trees = array();
2582

    
2583
  // Set tree that contains all features.
2584
  if ($add_default_feature_free) {
2585
    $options[UUID_DEFAULT_FEATURETREE] = t('Default Featuretree (contains all features)');
2586
    $feature_trees[] = cdm_ws_get(CDM_WS_TERMTREE, UUID_DEFAULT_FEATURETREE);
2587
  }
2588

    
2589
  // Get feature trees from database.
2590
  $persited_trees = cdm_ws_fetch_all(CDM_WS_TERMTREES, array("termType" => "Feature"));
2591
  if (is_array($persited_trees)) {
2592
    $feature_trees = array_merge($feature_trees, $persited_trees);
2593
  }
2594

    
2595
  foreach ($feature_trees as $featureTree) {
2596

    
2597
    if(!is_object($featureTree)){
2598
      continue;
2599
    }
2600
    // Do not add the DEFAULT_FEATURETREE again,
2601
    if ($featureTree->uuid != UUID_DEFAULT_FEATURETREE) {
2602
      $options[$featureTree->uuid] = $featureTree->representation_L10n;
2603
    }
2604

    
2605
    // Render the hierarchic tree structure
2606
    if (is_array( $featureTree->root->childNodes) && count( $featureTree->root->childNodes) > 0) {
2607

    
2608
      // Render the hierarchic tree structure.
2609
      $treeDetails = '<div class="featuretree_structure">'
2610
        . render_feature_tree_hierarchy($featureTree->uuid, $show_weight)
2611
        . '</div>';
2612

    
2613
      $form = array();
2614
      $form['featureTree-' .  $featureTree->uuid] = array(
2615
        '#type' => 'fieldset',
2616
        '#title' => 'Show details',
2617
        '#attributes' => array('class' => array('collapsible collapsed')),
2618
        // '#collapsible' => TRUE,
2619
        // '#collapsed' => TRUE,
2620
      );
2621
      $form['featureTree-' .  $featureTree->uuid]['details'] = array(
2622
        '#markup' => $treeDetails,
2623
      );
2624

    
2625
      $tree_representations[$featureTree->uuid] = drupal_render($form);
2626
    }
2627

    
2628
  } // END loop over feature trees
2629

    
2630
  // return $options;
2631
  return array('options' => $options, 'treeRepresentations' => $tree_representations);
2632
}
2633

    
2634
/**
2635
 * Provides the list of available classifications in form of an options array.
2636
 *
2637
 * The options array is suitable for drupal form API elements that allow multiple choices.
2638
 * @see http://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7#options
2639
 *
2640
 * The classifications are ordered alphabetically whereas the classification
2641
 * chosen as default will always appear on top of the array, followed by a
2642
 * blank line below.
2643
 *
2644
 * @param bool $add_none_option
2645
 *   is true an additional 'none' option will be added if and only if there are
2646
 *   more than one options. Defaults to FALSE
2647
 *
2648
 * @param $include_uuids
2649
 *   The taxon tree uuids to be included, other taxon trees will be filtered out.
2650
 *   You may want to use here:
2651
 *   variable_get(CDM_TAXONTREE_INCLUDES, [])
2652
 *
2653
 *
2654
 * @return array
2655
 *   classifications in an array as options for a form element that allows multiple choices.
2656
 */
2657
function cdm_get_taxontrees_as_options($add_none_option = FALSE, $include_uuids = []) {
2658

    
2659
  $taxonTrees = cdm_ws_fetch_all(CDM_WS_PORTAL_TAXONOMY);
2660

    
2661
  $default_classification_uuid = variable_get(CDM_TAXONOMICTREE_UUID, FALSE);
2662
  $default_classification_label = '';
2663

    
2664
  // add all classifications
2665
  $taxonomic_tree_options = array();
2666
  if ($add_none_option) {
2667
    $taxonomic_tree_options['NONE'] = ' '; // one Space character at beginning to force on top;
2668
  }
2669
  if ($taxonTrees) {
2670
    foreach ($taxonTrees as $tree) {
2671
      if(is_array($include_uuids) && count($include_uuids) > 0 && array_search($tree->uuid, $include_uuids) === FALSE){
2672
        continue;
2673
      }
2674
      if (!$default_classification_uuid || $default_classification_uuid != $tree->uuid) {
2675
        $taxonomic_tree_options[$tree->uuid] = $tree->titleCache;
2676
      } else {
2677
        $taxonomic_tree_options[$tree->uuid] = '  '; // two Space characters to force on top but below 'none' option , will be replaced below by titleCache
2678
        $default_classification_label = $tree->titleCache;
2679
      }
2680
    }
2681
  }
2682
  // oder alphabetically the space
2683
  asort($taxonomic_tree_options);
2684

    
2685
  // now set the labels for none
2686
  if ($add_none_option && count($taxonomic_tree_options) > 2) {
2687
    $taxonomic_tree_options['NONE'] =t('--- ALL ---');
2688
  } else {
2689
    unset($taxonomic_tree_options['NONE']);
2690
  }
2691

    
2692
  //   for default_classification
2693
  if (is_uuid($default_classification_uuid)) {
2694
    $taxonomic_tree_options[$default_classification_uuid] =
2695
      $default_classification_label ? $default_classification_label : '--- INVALID CHOICE ---'
2696
      . (count($taxonTrees) > 1 ? ' [' . t('DEFAULT CLASSIFICATION') . ']': '');
2697
  }
2698

    
2699
  return $taxonomic_tree_options;
2700
}
2701

    
2702
/**
2703
 * @todo Please document this function.
2704
 * @see http://drupal.org/node/1354
2705
 */
2706
function cdm_api_secref_cache_prefetch(&$secUuids) {
2707
  // Comment @WA: global variables should start with a single underscore
2708
  // followed by the module and another underscore.
2709
  global $_cdm_api_secref_cache;
2710
  if (!is_array($_cdm_api_secref_cache)) {
2711
    $_cdm_api_secref_cache = array();
2712
  }
2713
  $uniqueUuids = array_unique($secUuids);
2714
  $i = 0;
2715
  $param = '';
2716
  while ($i++ < count($uniqueUuids)) {
2717
    $param .= $secUuids[$i] . ',';
2718
    if (strlen($param) + 37 > 2000) {
2719
      _cdm_api_secref_cache_add($param);
2720
      $param = '';
2721
    }
2722
  }
2723
  if ($param) {
2724
    _cdm_api_secref_cache_add($param);
2725
  }
2726
}
2727

    
2728
/**
2729
 * @todo Please document this function.
2730
 * @see http://drupal.org/node/1354
2731
 */
2732
function cdm_api_secref_cache_get($secUuid) {
2733
  global $_cdm_api_secref_cache;
2734
  if (!is_array($_cdm_api_secref_cache)) {
2735
    $_cdm_api_secref_cache = array();
2736
  }
2737
  if (!array_key_exists($secUuid, $_cdm_api_secref_cache)) {
2738
    _cdm_api_secref_cache_add($secUuid);
2739
  }
2740
  return $_cdm_api_secref_cache[$secUuid];
2741
}
2742

    
2743
/**
2744
 * @todo Please document this function.
2745
 * @see http://drupal.org/node/1354
2746
 */
2747
function cdm_api_secref_cache_clear() {
2748
  global $_cdm_api_secref_cache;
2749
  $_cdm_api_secref_cache = array();
2750
}
2751

    
2752

    
2753
/**
2754
 * @todo Please document this function.
2755
 * @see http://drupal.org/node/1354
2756
 */
2757
function _cdm_api_secref_cache_add($secUuidsStr) {
2758
  global $_cdm_api_secref_cache;
2759
  $ref = cdm_ws_get(CDM_WS_REFERENCE, $secUuidsStr);
2760
  // Batch fetching not jet reimplemented thus:
2761
  /*
2762
  $assocRefSTOs = array(); if($refSTOs) { foreach($refSTOs as $ref){
2763
  $assocRefSTOs[$ref->uuid] = $ref; } $_cdm_api_secref_cache =
2764
  array_merge($_cdm_api_secref_cache, $assocRefSTOs); }
2765
  */
2766
  $_cdm_api_secref_cache[$ref->uuid] = $ref;
2767
}
2768

    
2769
/**
2770
 * Checks if the given uri starts with a cdm webservice url.
2771
 *
2772
 * Checks if the uri starts with the cdm webservice url stored in the
2773
 * Drupal variable 'cdm_webservice_url'.
2774
 * The 'cdm_webservice_url' can be set in the admins section of the portal.
2775
 *
2776
 * @param string $uri
2777
 *   The URI to test.
2778
 *
2779
 * @return bool
2780
 *   True if the uri starts with a cdm webservice url.
2781
 */
2782
function _is_cdm_ws_uri($uri) {
2783
  return str_beginsWith($uri, cdm_webservice_url('#EMPTY#'));
2784
}
2785

    
2786
/**
2787
 * @todo Please document this function.
2788
 * @see http://drupal.org/node/1354
2789
 */
2790
function queryString($elements) {
2791
  $query = '';
2792
  foreach ($elements as $key => $value) {
2793
    if (is_array($value)) {
2794
      foreach ($value as $v) {
2795
        $query .= (strlen($query) > 0 ? '&' : '') . $key . '=' . urlencode($v);
2796
      }
2797
    }
2798
    else {
2799
      $query .= (strlen($query) > 0 ? '&' : '') . $key . '=' . urlencode($value);
2800
    }
2801
  }
2802
  return $query;
2803
}
2804

    
2805
/**
2806
 * Compares the given CDM Term instances by the  representationL10n.
2807
 *
2808
 * Can also be used with TermDTOs. To be used in usort()
2809
 *
2810
 * @see http://php.net/manual/en/function.usort.php
2811
 *
2812
 * @param $term1
2813
 *   The first CDM Term instance
2814
 * @param $term2
2815
 *   The second CDM Term instance
2816
 * @return int
2817
 *   The result of the comparison
2818
 */
2819
function compare_terms_by_representationL10n($term1, $term2) {
2820

    
2821
  if (!isset($term1->representation_L10n)) {
2822
    $term1->representationL10n = '';
2823
  }
2824
  if (!isset($term2->representation_L10n)) {
2825
    $term2->representationL10n = '';
2826
  }
2827

    
2828
  return strcmp($term1->representation_L10n, $term2->representation_L10n);
2829
}
2830

    
2831
function compare_terms_by_order_index($term1, $term2) {
2832

    
2833

    
2834
  if (!isset($term1->orderIndex)) {
2835
    $a = 0;
2836
  } else {
2837
    $a = $term1->orderIndex;
2838
  }
2839
  if (!isset($term2->orderIndex)) {
2840
    $b = 0;
2841
  } else {
2842
    $b = $term2->orderIndex;
2843
  }
2844

    
2845
  if ($a == $b) {
2846
    return 0;
2847
  }
2848
  return ($a < $b) ? -1 : 1;
2849

    
2850
}
2851

    
2852

    
2853
/**
2854
 * Make a 'deep copy' of an array.
2855
 *
2856
 * Make a complete deep copy of an array replacing
2857
 * references with deep copies until a certain depth is reached
2858
 * ($maxdepth) whereupon references are copied as-is...
2859
 *
2860
 * @see http://us3.php.net/manual/en/ref.array.php
2861
 *
2862
 * @param array $array
2863
 * @param array $copy passed by reference
2864
 * @param int $maxdepth
2865
 * @param int $depth
2866
 */
2867
function array_deep_copy(&$array, &$copy, $maxdepth = 50, $depth = 0) {
2868
  if ($depth > $maxdepth) {
2869
    $copy = $array;
2870
    return;
2871
  }
2872
  if (!is_array($copy)) {
2873
    $copy = array();
2874
  }
2875
  foreach ($array as $k => &$v) {
2876
    if (is_array($v)) {
2877
      array_deep_copy($v, $copy[$k], $maxdepth, ++$depth);
2878
    }
2879
    else {
2880
      $copy[$k] = $v;
2881
    }
2882
  }
2883
}
2884

    
2885
/**
2886
 * Concatenated the uuids of the passed cdm entity with `,` as glue.
2887
 * The returned string is suitable for cdm webservices consuming UUIDList as
2888
 * parameter
2889
 *
2890
 * @param array $cdm_entities
2891
 *
2892
 * @return string
2893
 */
2894
function cdm_uuid_list_parameter_value(array $cdm_entities){
2895
  $uuids = [];
2896
  foreach ($cdm_entities as $entity){
2897
    if(isset($entity) && is_uuid($entity->uuid) ){
2898
      $uuids[] = $entity->uuid;
2899
    }
2900
  }
2901
  return  join(',', $uuids);
2902
}
2903

    
2904
/**
2905
 * Adds java script to create and enable a toggler for the cdm webservice debug block content.
2906
 *
2907
 */
2908
function _add_js_ws_debug() {
2909

    
2910
  $data_tables_js = '/js/DataTables-1.9.4/media/js/jquery.dataTables.min.js';
2911
  $colorbox_js = '/js/colorbox/jquery.colorbox-min.js';
2912
  if (variable_get('cdm_js_devel_mode', FALSE)) {
2913
    // use the developer versions of js libs
2914
    $data_tables_js = '/js/DataTables-1.9.4/media/js/jquery.dataTables.js';
2915
    $colorbox_js = '/js/colorbox/jquery.colorbox.js';
2916
  }
2917
  drupal_add_js(drupal_get_path('module', 'cdm_dataportal') . $data_tables_js,
2918
    array(
2919
      'type' => 'file',
2920
      'weight' => JS_LIBRARY,
2921
      'cache' => TRUE)
2922
    );
2923

    
2924
  drupal_add_js(drupal_get_path('module', 'cdm_dataportal') . $colorbox_js,
2925
    array(
2926
      'type' => 'file',
2927
      'weight' => JS_LIBRARY,
2928
      'cache' => TRUE)
2929
    );
2930
  drupal_add_css(drupal_get_path('module', 'cdm_dataportal') . '/js/colorbox/colorbox.css');
2931
  drupal_add_css(drupal_get_path('module', 'cdm_dataportal') . '/js/DataTables-1.9.4/media/css/cdm_debug_table.css');
2932

    
2933
  drupal_add_js(drupal_get_path('module', 'cdm_dataportal') . '/js/ws_debug_block.js',
2934
    array(
2935
      'type' => 'file',
2936
      'weight' => JS_LIBRARY,
2937
      'cache' => TRUE)
2938
    );
2939
  drupal_add_js(drupal_get_path('module', 'cdm_dataportal') . '/js/http-method-link.js',
2940
    array(
2941
    'type' => 'file',
2942
    'weight' => JS_LIBRARY,
2943
    'cache' => TRUE)
2944
    );
2945

    
2946
}
2947

    
2948
/**
2949
 * @todo Please document this function.
2950
 * @see http://drupal.org/node/1354
2951
 */
2952
function _no_classfication_uuid_message() {
2953
  if (!cdm_ws_get(CDM_WS_PORTAL_TAXONOMY)) {
2954
    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.');
2955
  }
2956
  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.');
2957
}
2958

    
2959
/**
2960
 * Implementation of hook flush_caches
2961
 *
2962
 * Add custom cache tables to the list of cache tables that
2963
 * will be cleared by the Clear button on the Performance page or whenever
2964
 * drupal_flush_all_caches is invoked.
2965
 *
2966
 * @author W.Addink <waddink@eti.uva.nl>
2967
 *
2968
 * @return array
2969
 *   An array with custom cache tables to include.
2970
 */
2971
function cdm_api_flush_caches() {
2972
  return array('cache_cdm_ws');
2973
}
2974

    
2975
/**
2976
 * Logs if the drupal variable 'cdm_debug_mode' ist set true to drupal_debug.txt in the site's temp directory.
2977
 *
2978
 * @param $data
2979
 *   The variable to log to the drupal_debug.txt log file.
2980
 * @param $label
2981
 *   (optional) If set, a label to output before $data in the log file.
2982
 *
2983
 * @return
2984
 *   No return value if successful, FALSE if the log file could not be written
2985
 *   to.
2986
 *
2987
 * @see cdm_dataportal_init() where the log file is reset on each requests
2988
 * @see dd()
2989
 * @see http://drupal.org/node/314112
2990
 *
2991
 */
2992
function cdm_dd($data, $label = NULL) {
2993
  if(module_exists('devel') && variable_get('cdm_debug_mode', FALSE) && file_stream_wrapper_get_class('temporary') ){
2994
    return dd($data, $label);
2995
  }
2996
}
2997

    
(5-5/12)