Project

General

Profile

Download (40.4 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/**
3
 * @file
4
 * Search related functions.
5
 */
6

    
7
define("SESSION_KEY_SEARCH_REGISTRATION_FILTER", "SESSION_KEY_SEARCH_REGISTRATION_FILTER");
8
define('SESSION_KEY_SEARCH_TAXONGRAPH_FOR_REGISTRATION_FILTER', 'SESSION_KEY_SEARCH_TAXONGRAPH_FOR_REGISTRATION_FILTER');
9

    
10
/**
11
 * Returns a Drupal path to a search form for a CDM webservice.
12
 *
13
 * For a given CDM webservice end-point, the drupal page path to the
14
 * according search form is returned.
15
 * cdm webservice end points are defined in constant variables like:
16
 * <code>CDM_WS_PORTAL_TAXON_FIND</code> and
17
 * <code>CDM_WS_PORTAL_TAXON_FINDBY_DESCRIPTIONELEMENT_FULLTEXT</code>
18
 *
19
 * @param string $ws_endpoint
20
 *   The cdm webservice endpoint for which to find the search form path.
21
 *
22
 * @return string
23
 *   The Drupal path found.
24
 */
25
function cdm_dataportal_search_form_path_for_ws($ws_endpoint) {
26
  static $form_ws_map = array(
27
    CDM_WS_PORTAL_TAXON_FIND => "cdm_dataportal/search",
28
    CDM_WS_PORTAL_TAXON_SEARCH => "cdm_dataportal/search",
29
    CDM_WS_PORTAL_TAXON_FINDBY_DESCRIPTIONELEMENT_FULLTEXT => "cdm_dataportal/search/taxon_by_description",
30
  );
31
  return $form_ws_map[$ws_endpoint];
32
}
33

    
34
/**
35
 * Prepares a form array for a general purpose search form.
36
 *
37
 * The form is used for general purpose search functionality in the
38
 * dataportal. The form returned is populated with all necessary fields
39
 * for internal processing and has the textfield element $form['query']
40
 * which holds the query term.
41
 *
42
 * @param string $action_path
43
 *   The Drupal path to be put into the action url to which the form will
44
 *   be submitted.
45
 * @param string $search_webservice
46
 *   The cdm-remote webservice to be used, valid values are defined by
47
 *   the constants: FIXME.
48
 * @param string $query_field_default_value
49
 *   A default text for the query field
50
 * @param string $query_field_description
51
 *   The description text for the query field
52
 *
53
 * @return array
54
 *   The prepared form array.
55
 */
56
function cdm_dataportal_search_form_prepare($action_path, $search_webservice, $query_field_default_value, $query_field_description) {
57

    
58

    
59
  $form['#method'] = 'get';
60
  $form['#action'] = url($action_path, array(
61
    'absolute' => TRUE,
62
  ));
63

    
64
  $form['ws'] = array(
65
    '#type' => 'hidden',
66
    '#value' => $search_webservice,
67
    '#name' => 'ws',
68
  );
69

    
70
  $form['query'] = array(
71
    '#weight' => 0,
72
    '#type' => 'textfield',
73
    '#size' => 68,
74
    // This causes the description to display also when hovering over
75
    // the textfield.
76
    // This is wanted behaviour for the simple seach but could
77
    // be disabled for the advances search.
78
    '#attributes' => array(
79
      'title' => $query_field_description,
80
    ),
81
    '#description' => $query_field_description,
82
    '#value' => $query_field_default_value,
83
    // '#description' => $query_field_description,
84
  );
85
  if(variable_get(SIMPLE_SEARCH_AUTO_SUGGEST)){
86
      $form['query']['#autocomplete_path'] = 'cdm_dataportal/taxon/autosuggest////';
87
  }
88

    
89
    $form['search'] = array(
90
    '#weight' => 3,
91
    '#tree' => TRUE,
92
    // '#type' => $advanced_form ? 'fieldset': 'hidden',
93
    '#title' => t('Options'),
94
  );
95

    
96
  // Clean URL get forms breaks if we don't give it a 'q'.
97
  if (!(bool) variable_get('clean_url', '0')) {
98
    $form['search']['q'] = array(
99
      '#type' => 'hidden',
100
      '#value' => $action_path,
101
      '#name' => 'q',
102
    );
103
  }
104

    
105
  $form['submit'] = array(
106
    '#weight' => 5,
107
    '#type' => 'submit',
108
    '#name' => '',
109
    '#value' => t('Search'),
110
  );
111

    
112
  return $form;
113
}
114

    
115
function cdm_dataportal_taxon_autosuggest($classificationUuid = NULL, $areaUuid = NULL, $status = NULL, $string) {
116
  $matches = array();
117

    
118
  $queryParams = array();
119
  $queryParams['query'] = $string.'*';
120
  if((is_null($classificationUuid) || $classificationUuid=='') && isset($_SESSION['cdm']['taxonomictree_uuid'])){
121
    $classificationUuid = $_SESSION['cdm']['taxonomictree_uuid'];// if no classification uuid is set use the current one
122
  }
123
  if($classificationUuid){
124
    $queryParams['classificationUuid'] = $classificationUuid;
125
  }
126
  if($areaUuid){
127
    $queryParams['area'] = $areaUuid;
128
  }
129
  if($status){
130
    $queryParams['status'] = $status ;
131
  }
132
  $queryParams['pageNumber'] = '0';
133
  $queryParams['pageSize'] = '10';
134
  $queryParams['doTaxa'] = true;
135
  $queryParams['doSynonyms'] = true;
136
  $queryParams['doMisappliedNames'] = true;
137
  $queryParams['doTaxaByCommonNames'] = true;
138

    
139
  $search_results = cdm_ws_get(CDM_WS_TAXON_SEARCH, NULL, queryString($queryParams));
140
  foreach($search_results->records as $record){
141
      $titleCache = $record->entity->titleCache;
142
      preg_match('/(.*) sec.*/', $titleCache, $trimmedTitle); //remove sec reference
143
      $trimmedTitle = trim($trimmedTitle[1]);
144
      $matches[$trimmedTitle] = check_plain($trimmedTitle);
145
  }
146
  drupal_json_output($matches);
147
}
148

    
149

    
150
  /**
151
 * Creates a search form for searching on taxa.
152
 *
153
 * If advanced $advanced_form id TRUE the form will offer additional choices
154
 *
155
 * @param array $form
156
 *   A drupal form array
157
 * @param array $form_state
158
 *   The drupal form state passed as reference
159
 * @param bool $advanced_form
160
 *   default is FALSE
161
 * @param bool $classification_select
162
 *   set TRUE to offer a classification selector in the form - default is FALSE
163
 *   if only available in the advanced mode
164
 *
165
 * @return array
166
 *   the form array
167
 */
168
function cdm_dataportal_search_taxon_form($form, &$form_state, $advanced_form = FALSE, $classification_select = TRUE)
169
{
170

    
171
    if ($form_state['build_info']['form_id'] == 'cdm_dataportal_search_blast_form') {
172
        $form = cdm_dataportal_search_blast_form($form, $form_state);
173
    } else {
174

    
175

    
176
        $query_field_default_value = (isset($_SESSION['cdm']['search']['query']) ? $_SESSION['cdm']['search']['query'] : '');
177

    
178
        if ($advanced_form || variable_get(SIMPLE_SEARCH_USE_LUCENE_BACKEND, FALSE)) {
179
            $search_service_endpoint = CDM_WS_PORTAL_TAXON_SEARCH;
180
        } else {
181
            $search_service_endpoint = CDM_WS_PORTAL_TAXON_FIND;
182
        }
183

    
184
        $form = cdm_dataportal_search_form_prepare(
185
            'cdm_dataportal/search/results/taxon',
186
            $search_service_endpoint,
187
            $query_field_default_value,
188
            t('Enter the name or part of a name you wish to search for.
189
          The asterisk  character * can be used as wildcard, but must not be used as first character.')
190
        );
191
    }
192
  if (!$advanced_form) {
193
    $form['query']['#size'] = 20;
194
  }
195

    
196
  $form['search']['pageSize'] = array(
197
    '#weight' => -1,
198
    '#type' => 'hidden',
199
    '#value' => variable_get('cdm_dataportal_search_items_on_page', 25),
200
  );
201

    
202
  $form['search']['pageNumber'] = array(
203
    '#weight' => -1,
204
    '#type' => 'hidden',
205
    '#value' => 0,
206
  );
207

    
208
  $search_taxa_mode_settings = get_array_variable_merged(
209
    CDM_SEARCH_TAXA_MODE,
210
    CDM_SEARCH_TAXA_MODE_DEFAULT
211
  );
212
  $preset_do_taxa = $search_taxa_mode_settings['doTaxa'] !== 0;
213
  $preset_do_synonyms = $search_taxa_mode_settings['doSynonyms'] !== 0;
214
  $preset_do_taxa_by_common_names = $search_taxa_mode_settings['doTaxaByCommonNames'] !== 0;
215
  $preset_do_misapplied_names = $search_taxa_mode_settings['doMisappliedNames'] !== 0;
216

    
217
  if ($advanced_form) {
218

    
219
    // --- ADVANCED SEARCH FORM ---
220
    //
221

    
222
    // Get presets from settings.
223
    $preset_classification_uuid = get_current_classification_uuid();
224

    
225
    // Overwrite presets by user choice stored in session.
226
    if (isset($_SESSION['cdm']['search'])) {
227
      $preset_do_taxa = (isset($_SESSION['cdm']['search']['doTaxa']) ? 1 : 0);
228
      $preset_do_synonyms = (isset($_SESSION['cdm']['search']['doSynonyms']) ? 1 : 0);
229
      $preset_do_misapplied_names = (isset($_SESSION['cdm']['search']['doMisappliedNames']) ? 1 : 0);
230
      $preset_do_taxa_by_common_names = (isset($_SESSION['cdm']['search']['doTaxaByCommonNames']) ? 1 : 0);
231
      if (isset($_SESSION['cdm']['search']['tree'])) {
232
        $preset_classification_uuid = $_SESSION['cdm']['search']['tree'];
233
      }
234
    }
235

    
236
    if ($classification_select === TRUE) {
237
      $form['search']['tree'] = array(
238
        '#title' => t('Classification'),
239
        '#weight' => 1,
240
        '#type' => 'select',
241
        '#default_value' => $preset_classification_uuid,
242
        '#options' => cdm_get_taxontrees_as_options(TRUE),
243
        '#description' => t('A filter to limit the search to a specific classification. Choosing <em>--- ALL ---</em> will disable this filter.'),
244
      );
245
    }
246

    
247
    // General search parameters.
248
    $form['search']['doTaxa'] = array(
249
      '#weight' => 2,
250
      '#type' => 'checkbox',
251
      '#title' => t('Include') . ' ' . t('accepted taxa'),
252
      '#value' => $preset_do_taxa,
253
    );
254
    $form['search']['doSynonyms'] = array(
255
      '#weight' => 3,
256
      '#type' => 'checkbox',
257
      '#title' => t('Include') . ' ' . t('synonyms'),
258
      '#value' => $preset_do_synonyms,
259
    );
260
    $form['search']['doMisappliedNames'] = array(
261
      '#weight' => 4,
262
      '#type' => 'checkbox',
263
      '#title' => t('Include') . ' ' . t('misapplied names'),
264
      '#value' => $preset_do_misapplied_names,
265
    );
266
    $form['search']['doTaxaByCommonNames'] = array(
267
      '#weight' => 5,
268
      '#type' => 'checkbox',
269
      '#title' => t('Include') . ' ' . t('common names'),
270
      '#value' => $preset_do_taxa_by_common_names,
271
    );
272

    
273
    $area_term_dtos = cdm_ws_fetch_all(
274
      CDM_WS_DESCRIPTION_NAMEDAREAS_IN_USE,
275
      array('includeAllParents' => 'true')
276
    );
277

    
278
    // create map: term_uuid => term
279
    $term_map = array();
280
    foreach ($area_term_dtos as $term_dto) {
281
      $term_map[$term_dto->uuid] = $term_dto;
282
    }
283

    
284
    $term_tree = array();
285
    // mixed_vocabularies will contain the uuid vocabularies which
286
    // also contain terms of foreign vocabularies due to the term
287
    // hierarchy
288
    $mixed_vocabularies = array();
289

    
290
    // Build hierarchy of the terms regardless of the vocabulary.
291
    foreach ($term_map as $term_dto) {
292
      if (!empty($term_dto->partOfUuid)) {
293
        // Children.
294
        $parent =& $term_map[$term_dto->partOfUuid];
295
        if ($parent) {
296
          if (!isset($parent->children)) {
297
            $parent->children = array();
298
          }
299
          $parent->children[$term_dto->uuid] = $term_dto;
300
          if ($parent->vocabularyUuid != $term_dto->vocabularyUuid) {
301
            $mixed_vocabularies[$parent->vocabularyUuid] = $parent->vocabularyUuid;
302
          }
303
        }
304
      }
305
      else {
306
        // group root nodes by vocabulary
307
        if (!isset($term_tree[$term_dto->vocabularyUuid])) {
308
          $term_tree[$term_dto->vocabularyUuid] = array();
309
        }
310
        $term_tree[$term_dto->vocabularyUuid][$term_dto->uuid] = $term_dto;
311
      }
312
    }
313

    
314
    $show_area_filter = ! variable_get(CDM_SEARCH_AREA_FILTER_PRESET, '');
315

    
316
    if($show_area_filter){
317
      drupal_add_js(drupal_get_path('module', 'cdm_dataportal') . '/js/search_area_filter.js');
318

    
319
      drupal_add_js('jQuery(document).ready(function() {
320
        jQuery(\'#edit-search-areas\').search_area_filter(\'#edit-search-areas-areas-filter\');
321
      });
322
      ', array('type' => 'inline'));
323

    
324
      $form['search']['areas'] = array(
325
        '#type' => 'fieldset',
326
        '#title' => t('Filter by distribution areas'),
327
        '#description' => t('The search will return taxa having distribution
328
        information for at least one of the selected areas.') . ' '
329
          .(count($term_tree) > 1 ? t('The areas are grouped
330
        by the vocabularies to which the highest level areas belong.') : ''),
331
      );
332
      $form['search']['areas']['areas_filter'] = array(
333
        '#type' => 'textfield',
334
        '#description' => t('Type to filter the areas listed below.'),
335
      );
336
      $vocab_cnt = 0;
337
      $areas_defaults = array();
338
      if (isset($_SESSION['cdm']['search']['area'])) {
339
        $areas_defaults = explode(',', $_SESSION['cdm']['search']['area']);
340
      }
341
      _add_js_resizable_element('.resizable-box', true);
342
      foreach ($term_tree as $vocab_uuid => $term_dto_tree) {
343
        $vocabulary = cdm_ws_get(CDM_WS_TERMVOCABULARY, array($vocab_uuid));
344
        $areas_options = term_tree_as_options($term_dto_tree);
345
        $form['search']['areas']['area'][$vocab_cnt++] = array(
346
          '#prefix' => '<strong>' . $vocabulary->representation_L10n
347
            . (isset($mixed_vocabularies[$vocab_uuid]) ? ' <span title="Contains terms of at least one other area vocabulary.">(' . t('mixed') . ')</span>': '')
348
            . '</strong><div class="resizable-container"><div class="resizable-box">',
349
          '#type' => 'checkboxes',
350
          '#default_value' => $areas_defaults,
351
          '#options' => $areas_options,
352
          '#suffix' => '</div></div>'
353
        );
354
      }
355
    }
356

    
357
  }
358
  else {
359
    // --- SIMPLE SEARCH FORM ---
360
    //
361

    
362
    // Overwrite presets by user choice stored in session.
363
    if (isset($_SESSION['cdm']['search'])) {
364
      $preset_do_misapplied_names = (isset($_SESSION['cdm']['search']['doMisappliedNames']) ? 1 : 0);
365
    }
366

    
367
    $form['search']['doTaxa'] = array(
368
      '#weight' => -2,
369
      '#type' => 'hidden',
370
      '#value' => $preset_do_taxa,
371
    );
372
    $form['search']['doSynonyms'] = array(
373
      '#weight' => -3,
374
      '#type' => 'hidden',
375
      '#value' => $preset_do_synonyms,
376
    );
377
    $form['search']['doMisappliedNames'] = array(
378
      '#weight' => -4,
379
      '#type' => 'checkbox',
380
      '#title' => t('Misapplied names'),
381
      '#value' => $preset_do_misapplied_names,
382
    );
383
    $form['search']['doTaxaByCommonNames'] = array(
384
      '#weight' => -5,
385
      '#type' => 'hidden',
386
      '#value' => $preset_do_taxa_by_common_names,
387
    );
388
  }
389

    
390
  return $form;
391
}
392

    
393
/**
394
 * Creates a search form for searching on taxa.
395
 *
396
 * If advanced $advanced_form id TRUE the form will offer additional choices
397
 *
398
 * @param array $form
399
 *   A drupal form array
400
 * @param array $form_state
401
 *   The drupal form state passed as reference
402
 * @param bool $advanced_form
403
 *   default is FALSE
404
 * @param bool $classification_select
405
 *   set TRUE to offer a classification selector in the form - default is FALSE
406
 *   if only available in the advanced mode
407
 *
408
 * @return array
409
 *   the form array
410
 */
411
function cdm_dataportal_search_blast_form($form, &$form_state) {
412

    
413
    $query_field_default_value = (isset($_SESSION['cdm']['search']['query']) ? $_SESSION['cdm']['search']['query'] : '');
414

    
415
    $search_service_endpoint = CDM_SEARCH_BLAST_SERVICE_URI;
416

    
417

    
418
    $form = cdm_dataportal_search_blast_form_prepare(
419
        'cdm_dataportal/search/results/specimen',
420
        $search_service_endpoint,
421
        $query_field_default_value,
422
        t('Enter the sequence or part of a sequence you wish to search for.')
423
    );
424

    
425

    
426

    
427
    $form['search']['pageSize'] = array(
428
        '#weight' => -1,
429
        '#type' => 'hidden',
430
        '#value' => variable_get('cdm_dataportal_search_items_on_page', 25),
431
    );
432

    
433
    $form['search']['pageNumber'] = array(
434
        '#weight' => -1,
435
        '#type' => 'hidden',
436
        '#value' => 0,
437
    );
438

    
439

    
440

    
441

    
442

    
443
    return $form;
444
}
445

    
446
/**
447
 * Prepares a form array for a general purpose search form.
448
 *
449
 * The form is used for general purpose search functionality in the
450
 * dataportal. The form returned is populated with all necessary fields
451
 * for internal processing and has the textfield element $form['query']
452
 * which holds the query term.
453
 *
454
 * @param string $action_path
455
 *   The Drupal path to be put into the action url to which the form will
456
 *   be submitted.
457
 * @param string $search_webservice
458
 *   The cdm-remote webservice to be used, valid values are defined by
459
 *   the constants: FIXME.
460
 * @param string $query_field_default_value
461
 *   A default text for the query field
462
 * @param string $query_field_description
463
 *   The description text for the query field
464
 * @param string $process
465
 *   The value for #process, if NULL (default), 'cdm_dataportal_search_process'
466
 *   is used.
467
 *
468
 * @return array
469
 *   The prepared form array.
470
 */
471
function cdm_dataportal_search_blast_form_prepare($action_path, $search_webservice, $query_field_default_value, $query_field_description, $process = NULL) {
472

    
473
    if ($process == NULL) {
474
        $process = 'cdm_dataportal_search_process';
475
    }
476

    
477
    $form['#method'] = 'get';
478
    //
479
    //  $form['#process'] = array(
480
    //  $process => array(),
481
    //  );
482
    //
483
    $form['#action'] = url($action_path, array(
484
        'absolute' => TRUE,
485
    ));
486

    
487
    $form['ws'] = array(
488
        '#type' => 'hidden',
489
        '#value' => $search_webservice,
490
        '#name' => 'ws',
491
    );
492

    
493
    $form['query'] = array(
494
        '#weight' => 0,
495
        '#type' => 'textarea',
496
        '#size' => 68,
497
        // This causes the description to display also when hovering over
498
        // the textfield.
499
        // This is wanted behaviour for the simple seach but could
500
        // be disabled for the advances search.
501
        '#attributes' => array(
502
            'title' => $query_field_description,
503
        ),
504
        '#description' => $query_field_description,
505
       // '#value' => $query_field_default_value,
506
        // '#description' => $query_field_description,
507
    );
508

    
509

    
510
    $form['search'] = array(
511
        '#weight' => 3,
512
        '#tree' => TRUE,
513
        // '#type' => $advanced_form ? 'fieldset': 'hidden',
514
        '#title' => t('Options'),
515
    );
516

    
517
    // Clean URL get forms breaks if we don't give it a 'q'.
518
    if (!(bool) variable_get('clean_url', '0')) {
519
        $form['search']['q'] = array(
520
            '#type' => 'hidden',
521
            '#value' => $action_path,
522
            '#name' => 'q',
523
        );
524
    }
525

    
526
    $form['search']['word_size'] = array(
527
        '#weight' => 1,
528
        '#type' => 'textfield',
529
        '#title' => t('Word size'),
530
        '#default_value' => 7,
531
        '#description' => t('Length of initial exact match'),
532
    );
533

    
534
    $form['search']['reward'] = array(
535
        '#weight' => 2,
536
        '#type' => 'textfield',
537
        '#title' => t('Reward'),
538
        '#default_value' => 1,
539
        '#description' => t('Reward for Matching'),
540
    );
541

    
542
    $form['search']['penalty'] = array(
543
        '#weight' => 3,
544
        '#type' => 'textfield',
545
        '#title' => t('Penalty'),
546
        '#default_value' => -2,
547
        '#description' => t('Penalty for mismatching'),
548
    );
549

    
550
    $form['search']['gap_open'] = array(
551
        '#weight' => 4,
552
        '#type' => 'textfield',
553
        '#title' => t('Gap open'),
554
        '#default_value' => 5,
555
        '#description' => t('Cost to open a gap'),
556
    );
557

    
558
    $form['search']['gap_extend'] = array(
559
        '#weight' => 5,
560
        '#type' => 'textfield',
561
        '#title' => t('Gap extend'),
562
        '#default_value' => -2,
563
        '#description' => t('Cost for extend a gap'),
564
    );
565

    
566
    $form['submit'] = array(
567
        '#weight' => 5,
568
        '#type' => 'submit',
569
        '#name' => '',
570
        '#value' => t('Search'),
571
    );
572

    
573
    return $form;
574
}
575
/**
576
 * Wrapper function for cdm_dataportal_search_taxon_form().
577
 *
578
 * This function makes ot possible possible to just pass the
579
 * correct $form_id 'cdm_dataportal_search_taxon_form_advanced' to
580
 * drupal_get_form like:
581
 * drupal_get_form('cdm_dataportal_search_taxon_form_advanced');
582
 *
583
 * @param array $form
584
 *   A drupal form array
585
 * @param array $form_state
586
 *   The drupal form state passed as reference
587
 *
588
 * @return array
589
 *   The form array
590
 */
591
function cdm_dataportal_search_taxon_form_advanced($form, &$form_state) {
592
  return cdm_dataportal_search_taxon_form($form, $form_state, TRUE);
593
}
594

    
595
/**
596
 * Form for searching taxa by the findByDescriptionElementFullText rest service.
597
 */
598
function cdm_dataportal_search_taxon_by_description_form() {
599
  $query_field_default_value = (isset($_SESSION['cdm']['search']['query']) ? $_SESSION['cdm']['search']['query'] : '');
600

    
601
  $form = cdm_dataportal_search_form_prepare(
602
    'cdm_dataportal/search/results/taxon',
603
    CDM_WS_PORTAL_TAXON_FINDBY_DESCRIPTIONELEMENT_FULLTEXT,
604
    $query_field_default_value,
605
    t("Enter the text you wish to search for. The asterisk character * can be
606
        used as wildcard, but must not be used as first character. Terms can be combined with 'AND'. To search for a
607
        full phrase enclose the terms in parentheses. For more syntactical
608
        options please refer to the !link.",
609
      array(
610
        '!link' => l(
611
          t('Apache Lucene - Query Parser Syntax'),
612
          'http://lucene.apache.org/core/old_versioned_docs/versions/2_9_1/queryparsersyntax.html', array(
613
            'attributes' => array(
614
              'absolute' => TRUE,
615
              'html' => TRUE),
616
          )
617
        ),
618
      )
619
    )
620
  );
621

    
622
  $form['search']['tree'] = array(
623
    '#weight' => -1,
624
    '#type' => 'hidden',
625
    '#value' => get_current_classification_uuid(),
626
  );
627

    
628
  $form['search']['hl'] = array(
629
    '#weight' => -1,
630
    '#type' => 'hidden',
631
    '#value' => 1,
632
  );
633

    
634
  // Only available to admins:
635
  if (!isset($_SESSION['cdm']['search']['clazz'])) {
636
    $_SESSION['cdm']['search']['clazz'] = '';
637
  }
638
  if (module_exists("user") && user_access('administer')) {
639
    $form['search']['clazz'] = array(
640
      '#type' => 'select',
641
      '#title' => t('Limit to description item type'),
642
      '#default_value' => $_SESSION['cdm']['search']['clazz'],
643
      '#options' => cdm_descriptionElementTypes_as_option(TRUE),
644
    );
645
  }
646

    
647
  $profile_feature_tree = get_profile_feature_tree();
648
  $feature_options = _featureTree_nodes_as_feature_options($profile_feature_tree->root);
649
  if (isset($_SESSION['cdm']['search']['features'])) {
650
    $form['search']['features'] = array(
651
      '#type' => 'checkboxes',
652
      '#title' => t('Limit to selected features'),
653
      '#default_value' => $_SESSION['cdm']['search']['features'],
654
      '#options' => $feature_options,
655
    );
656
  }
657
  else {
658
    $form['search']['features'] = array(
659
      '#type' => 'checkboxes',
660
      '#title' => t('Limit to selected features'),
661
      '#options' => $feature_options,
662
    );
663
  }
664
  return $form;
665
}
666

    
667
/**
668
 * Processes the query parameters of the search form.
669
 *
670
 * Reads the query parameters from $_REQUEST and modifies and adds additional
671
 * query parameters if necessary.
672
 *
673
 *  - Filters $_REQUEST by a list of valid request parameters
674
 *  - modifies geographic_range parameters
675
 *  - adds taxon tree uuid if it is missing and if it should not be
676
 *    ignored (parameter value = 'IGNORE')
677
 *  - and more
678
 *
679
 * @param $search_endpoint string
680
 *    The web service endpoint which will be used for executing the search.
681
 *    Usually one of CDM_WS_PORTAL_TAXON_SEARCH, CDM_WS_PORTAL_TAXON_FIND,
682
 *    CDM_WS_PORTAL_TAXON_FINDBY_DESCRIPTIONELEMENT_FULLTEXT.
683
 * @return array
684
 *   the processed request parameters submitted by the search form and
685
 *   also stores them in $_SESSION['cdm']['search']
686
 */
687
function cdm_dataportal_search_request($search_endpoint)
688
{
689

    
690
  $form_params = array();
691

    
692
  if (isset($_REQUEST['search']) && is_array($_REQUEST['search'])) {
693
    array_deep_copy($_REQUEST['search'], $form_params);
694
  }
695

    
696
  if (isset($_REQUEST['pager']) && is_array($_REQUEST['pager'])) {
697
    $form_params = array_merge($form_params, $_REQUEST['pager']);
698
  }
699

    
700
  $form_params['query'] = trim($_REQUEST['query']);
701

    
702

    
703
  // --- handle geographic range
704
  // Split of geographic range.
705
  unset($form_params['areas']);
706

    
707
  $area_filter_preset = null;
708
  if (variable_get(CDM_SEARCH_AREA_FILTER_PRESET, '')) {
709
    $area_filter_preset = explode(',', variable_get(CDM_SEARCH_AREA_FILTER_PRESET, ''));
710
  }
711

    
712
  $area_uuids = array();
713
  if($area_filter_preset){
714
    $area_uuids = $area_filter_preset;
715
  }
716
  elseif (isset($_REQUEST['search']['areas']['area']) && is_array($_REQUEST['search']['areas']['area'])) {
717
    foreach ($_REQUEST['search']['areas']['area'] as $areas) {
718
      $area_uuids = array_merge($area_uuids, $areas);
719
    }
720
    // The area filter is limited to areas with non absent distribution status
721
    $presence_terms_options = cdm_vocabulary_as_option(UUID_PRESENCE_ABSENCE_TERM, null, FALSE, array('absenceTerm' => '/false/'));
722
    $presence_term_uuids = array_keys($presence_terms_options);
723
    $form_params['status'] = $presence_term_uuids;
724
  }
725
  if(count($area_uuids) > 0){
726
    $form_params['area'] = implode(',', $area_uuids);
727
  }
728

    
729
  // Simple search will not submit a 'tree' query parameter,
730
  // so we add it here from what is stored in the session unless
731
  // SIMPLE_SEARCH_IGNORE_CLASSIFICATION is checked in the settings.
732
  if (!isset($form_params['tree']) && !variable_get(SIMPLE_SEARCH_IGNORE_CLASSIFICATION, 0)) {
733
    $form_params['tree'] = get_current_classification_uuid();
734
  }
735
  // Store in session.
736
  $_SESSION['cdm']['search'] = $form_params;
737

    
738
  // ----------- further processing that must not be store in the session --------- //
739

    
740
  if($search_endpoint == CDM_WS_PORTAL_TAXON_SEARCH){
741
    // HACK to allow using dot characters
742
    $form_params['query'] = str_replace('.', '*', $form_params['query']);
743
    // lucene based taxon search always as phrase search if the query string contains a whitespace --> enclose it in "
744
    if(preg_match("/\s+/", $form_params['query'])){
745
      if(!str_beginsWith($form_params['query'], '"')){
746
        $form_params['query'] = '"' . $form_params['query'];
747
      }
748
      if(!str_endsWith($form_params['query'], '"')){
749
        $form_params['query'] = $form_params['query'] . '"' ;
750
      }
751
    }
752
  }
753

    
754
  // If the 'NONE' classification has been chosen (advanced search)
755
  // delete the tree information to avoid unknown uuid exceptions in the
756
  // cdm service.
757
  if (isset($form_params['tree'])
758
    && ($form_params['tree'] == 'NONE' || !is_uuid($form_params['tree']))
759
  ) {
760
    // $form_params['ignore_classification'] =  TRUE;
761
    unset($form_params['tree']);
762
  }
763
  // else {
764
  //   $form_params['ignore_classification'] =  NULL;
765
  // }
766

    
767

    
768
  return $form_params;
769
}
770

    
771
/**
772
 * Processes the query parameters of the blast search form.
773
 *
774
 * Reads the query parameters from $_REQUEST and modifies and adds additional
775
 * query parameters if necessary.
776
 *
777
 *  - Filters $_REQUEST by a list of valid request parameters
778
 *
779
 *
780
 * @param $search_endpoint string
781
 *    The web service endpoint which will be used for executing the search.
782
 *
783
 * @return array
784
 *   the processed request parameters submitted by the search form and
785
 *   also stores them in $_SESSION['cdm']['search']
786
 */
787
function cdm_dataportal_blast_search_request($search_endpoint)
788
{
789
    $form_params = array();
790

    
791
    if (isset($_REQUEST['search']) && is_array($_REQUEST['search'])) {
792
        array_deep_copy($_REQUEST['search'], $form_params['data']);
793
    }
794
    $form_params['data'] = formatWSParams($_REQUEST['search']);
795
    $form_params['query']= trim($_REQUEST['query']).$form_params['data'];
796
    // Store in session.
797
    $_SESSION['cdm']['search'] = $form_params;
798
    return $form_params;
799
}
800

    
801
/**
802
 * Provides the classification to which the last search has been limited to..
803
 *
804
 * This function should only be used after the cdm_dataportal_search_taxon_execute()
805
 * handler has been run, otherwise it will return the information from the last
806
 * search executed. The information is retrieved from
807
 * the $_SESSION variable:  $_SESSION['cdm']['search']['tree']
808
 *
809
 * @return object
810
 *   the CDM classification instance which has been used a filter for the
811
 *   last processed search
812
 *   or NULL, it it was on all classifications
813
 */
814
function cdm_dataportal_searched_in_classification() {
815

    
816
  $classification = &drupal_static(__FUNCTION__);
817

    
818
  if (!isset($classification)) {
819
    if (isset($_SESSION['cdm']['search']['tree'])) {
820
      $classification = cdm_ws_get(CDM_WS_PORTAL_TAXONOMY, ($_SESSION['cdm']['search']['tree']));
821
    }
822
    else {
823
      $classification = FALSE;
824
    }
825
  }
826

    
827
  return $classification !== FALSE ? $classification : NULL;
828
}
829

    
830
/**
831
 * Removed the drupal internal form parameters 'form_id', 'form_token', 'form_build_id' from the request array.
832
 *
833
 * @param $request array
834
 *   Pass $_REQUEST as paramter
835
 * @return array
836
 *  The $request array without drupal internal form parameters
837
 */
838
function remove_drupal_form_params($request) {
839

    
840
  static $exclude_keys = array('form_id', 'form_token', 'form_build_id');
841
  $request_sanitized = array();
842
  foreach ($request as $key => $value) {
843
    if(!array_search($key, $exclude_keys)){
844
      $request_sanitized[$key] = $value;
845
    }
846
  }
847

    
848
  return $request_sanitized;
849
}
850

    
851
/**
852
 * Sends a search request to the cdm server.
853
 *
854
 * The parameters to build the query are taken obtained by calling
855
 * cdm_dataportal_search_request() which reads the query parameters
856
 * from $_REQUEST and add additional query parameters if nessecary.
857
 *
858
 * @see cdm_dataportal_search_request()
859
 */
860
function cdm_dataportal_search_taxon_execute() {
861

    
862
  // Store as last search in session.
863
  $_SESSION['cdm']['last_search'] = $_SERVER['REQUEST_URI'];
864

    
865
  // Validate the search webservice parameter:
866
  if (!isset($_REQUEST['ws'])) {
867
    drupal_set_message(
868
      t("Invalid search, webservice parameter 'ws' is missing"), 'warning'
869
    );
870
    return NULL;
871
  }
872
  if (!cdm_dataportal_search_form_path_for_ws($_REQUEST['ws'])) {
873
    // Endpoint is unknown.
874
    drupal_set_message(
875
      t("Invalid search webservice parameter 'ws' given"), 'warning'
876
    );
877
    return NULL;
878
  }
879

    
880
  // Read the query parameters from $_REQUEST and add additional query
881
  // parameters if necessary.
882
  $request_params = cdm_dataportal_search_request($_REQUEST['ws']);
883

    
884
  $taxon_pager = cdm_ws_get($_REQUEST['ws'], NULL, queryString($request_params));
885

    
886
  return $taxon_pager;
887
}
888

    
889
/**
890
 * Sends a search request to the cdm server.
891
 *
892
 * The parameters to build the query are taken obtained by calling
893
 * cdm_dataportal_search_request() which reads the query parameters
894
 * from $_REQUEST and add additional query parameters if nessecary.
895
 *
896
 * @see cdm_dataportal_search_request()
897
 */
898
function cdm_dataportal_search_blast_execute() {
899

    
900
    // Store as last search in session.
901
    $_SESSION['cdm']['last_blast_search'] = $_SERVER['REQUEST_URI'];
902

    
903
    // Validate the search webservice parameter:
904
    if (!isset($_REQUEST['ws'])) {
905
        drupal_set_message(
906
            t("Invalid search, webservice parameter 'ws' is missing"), 'warning'
907
        );
908
        return NULL;
909
    }
910
//    if (!cdm_dataportal_search_form_path_for_ws($_REQUEST['ws'])) {
911
//        // Endpoint is unknown.
912
//        drupal_set_message(
913
//            t("Invalid search webservice parameter 'ws' given"), 'warning'
914
//        );
915
//        return NULL;
916
//    }
917

    
918
    // Read the query parameters from $_REQUEST and add additional query
919
    // parameters if necessary.
920
    $request_params = cdm_dataportal_blast_search_request($_REQUEST['ws']);
921
   // $url = drupal_http_build_query($_REQUEST['ws'], $request_params);
922
    $request_params['timeout'] = 200;
923
    $taxon_pager = drupal_http_request($_REQUEST['ws'].'?sequence='.$request_params['query'], $request_params);
924

    
925
    return $taxon_pager;
926
}
927

    
928

    
929

    
930
/**
931
 * Sends a request for a registrations filter search to the cdm server.
932
 */
933
function cdm_dataportal_search_registrations_filter_execute()
934
{
935

    
936
  static $query_param_map = array(
937
    'identifier' => 'identifierFilterPattern',
938
    'taxon_name'=> 'taxonNameFilterPattern',
939
    'reference_citation' => 'referenceFilterPattern',
940
    'type_designation_status_uuids' => 'typeDesignationStatusUuids',
941
  );
942

    
943
  $session_key = SESSION_KEY_SEARCH_REGISTRATION_FILTER;
944
  $request_params = cdm_dataportal_search_request_params($session_key, $query_param_map);
945

    
946
  // cleanup
947
  if(isset($request_params['typeDesignationStatusUuids'])){
948
    if(!$request_params['typeDesignationStatusUuids']
949
      || $request_params['typeDesignationStatusUuids'] == "0"
950
      || (isset($request_params['typeDesignationStatusUuids'][0]) && !$request_params['typeDesignationStatusUuids'][0])){
951
      unset($request_params['typeDesignationStatusUuids']);
952
    }
953
  }
954
  if(isset($request_params['taxonNameFilterPattern'])){
955
    // trim and remove empty taxon name query strings
956
    $request_params['taxonNameFilterPattern'] = trim($request_params['taxonNameFilterPattern']);
957
    if(!$request_params['taxonNameFilterPattern']){
958
      unset($request_params['taxonNameFilterPattern']);
959
    }
960
  }
961
  // reference_citation
962
  if(isset($request_params['referenceFilterPattern'])){
963
    // trim and remove empty taxon name query strings
964
    $request_params['referenceFilterPattern'] = trim($request_params['referenceFilterPattern']);
965
    if(!$request_params['referenceFilterPattern']){
966
      unset($request_params['referenceFilterPattern']);
967
    }
968
  }
969

    
970
  $registration_pager = cdm_ws_get('registrationDTO/find', NULL, queryString($request_params));
971

    
972
  return $registration_pager;
973
}
974

    
975
/**
976
 * Sends a request for a registrations taxongraph search to the cdm server.
977
 */
978
function cdm_dataportal_search_registrations_taxongraph_execute()
979
{
980

    
981
  static $query_param_map = array(
982
    'taxon_name'=> 'taxonNameFilter'
983
  );
984

    
985
  $session_key = SESSION_KEY_SEARCH_TAXONGRAPH_FOR_REGISTRATION_FILTER;
986
  $request_params = cdm_dataportal_search_request_params($session_key, $query_param_map);
987

    
988
  // cleanup
989
  if(isset($request_params['taxonNameFilter'])){
990
    // trim and remove empty taxon name query strings
991
    $request_params['taxonNameFilter'] = trim($request_params['taxonNameFilter']);
992
    if(!$request_params['taxonNameFilter']){
993
      unset($request_params['taxonNameFilter']);
994
    }
995
  }
996

    
997
  $registration_pager = cdm_ws_get('registrationDTO/findInTaxonGraph', NULL, queryString($request_params));
998

    
999
  return $registration_pager;
1000
}
1001

    
1002
/**
1003
 * @param $session_key
1004
 * @param $query_param_map
1005
 * @return array
1006
 */
1007
function cdm_dataportal_search_request_params($session_key, $query_param_map)
1008
{
1009
  // Read the query parameters from $_REQUEST and add additional query
1010
  // parameters if necessary.
1011
  $request_params = array();
1012

    
1013
  $request = remove_drupal_form_params($_REQUEST);
1014

    
1015
  if (count($request) > 0) {
1016
    $_SESSION['cdm'][$session_key] = $request;
1017
    foreach ($query_param_map as $filter_key => $query_param) {
1018
      if (isset($request[$filter_key])) {
1019
        $request_params[$query_param] = $request[$filter_key];
1020
      }
1021
    }
1022
    if (isset($request['pager']['pageNumber'])) {
1023
      $request_params['pageNumber'] = $request['pager']['pageNumber'];
1024
    }
1025
  }
1026

    
1027
  if (count($request_params) == 0 && isset($_SESSION['cdm'][$session_key])) {
1028
    foreach ($query_param_map as $filter_key => $query_param) {
1029
      if (isset($_SESSION['cdm'][$session_key][$filter_key])) {
1030
        $request_params[$query_param] = $_SESSION['cdm'][$session_key][$filter_key];
1031
      }
1032
    }
1033
    if (isset($_SESSION['cdm'][$session_key]['pager']['pageNumber'])) {
1034
      $request_params['pageNumber'] = $_SESSION['cdm'][$session_key]['pager']['pageNumber'];
1035
    }
1036
  }
1037
  return $request_params;
1038
}
1039

    
1040
/**
1041
 * Transforms the termDTO tree into options array.
1042
 *
1043
 *   TermDto:
1044
 *      - partOfUuid:
1045
 *      - representation_L10n:
1046
 *      - representation_L10n_abbreviatedLabel:
1047
 *      - uuid:
1048
 *      - vocabularyUuid:
1049
 *      - children: array of TermDto
1050
 *
1051
 * The options array is suitable for drupal form API elements that
1052
 * allow multiple choices.
1053
 * @see http://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7#options
1054
 *
1055
 * @param array $term_dto_tree
1056
 *   a hierarchic array of CDM TermDto instances, with additional
1057
 * 'children' field:
1058
 * @param array $options
1059
 *   Internally used for recursive calls
1060
 * @param string $prefix
1061
 *   Internally used for recursive calls
1062
 *
1063
 * @return array
1064
 *   the terms in an array as options for a form element that allows
1065
 *   multiple choices.
1066
 */
1067
function term_tree_as_options($term_dto_tree, &$options = array(), $prefix = '') {
1068

    
1069
  uasort($term_dto_tree, 'compare_terms_by_order_index');
1070
  foreach ($term_dto_tree as $uuid => $dto) {
1071
    $label = $prefix . '<span class="child-label">'
1072
      .  $dto->representation_L10n
1073
      . '</span><span class="child-label-abbreviated"> (' . $dto->representation_L10n_abbreviatedLabel . ')</span>';
1074
    $options[$uuid] = $label;
1075
    if (isset($dto->children) && is_array($dto->children)) {
1076
      term_tree_as_options(
1077
        $dto->children,
1078
        $options, $prefix
1079
          . '<span data-cdm-parent="' . $uuid . '" class="parent"></span>'
1080
      );
1081
    }
1082
  }
1083

    
1084
  return $options;
1085
}
1086

    
1087

    
1088
function cdm_dataportal_search_registration_filter_form($form, &$form_state) {
1089

    
1090
  static $filter_presets_empty = array(
1091
    'identifier'=> null,
1092
    'taxon_name'=> null,
1093
    'reference_citation'=> null,
1094
    'type_designation_status_uuids' => null
1095
  );
1096

    
1097
  _add_font_awesome_font();
1098

    
1099
  if(isset($_REQUEST['q']) && ($_REQUEST['q'] == 'cdm_dataportal/registration-search/filter' || $_REQUEST['q'] == 'cdm_dataportal/registration-search')){
1100
    // read the $request_params only if it was send from this form
1101
    $request_params = remove_drupal_form_params($_REQUEST);
1102
  } else {
1103
    $request_params = array();
1104
  }
1105
  $filter_presets = (isset($_SESSION['cdm'][SESSION_KEY_SEARCH_REGISTRATION_FILTER]) ? $_SESSION['cdm'][SESSION_KEY_SEARCH_REGISTRATION_FILTER] : array());
1106
  $filter_presets = array_merge($filter_presets_empty, $filter_presets, $request_params);
1107
  $form['#action'] =  url('/cdm_dataportal/registration-search/filter');
1108
  $form['#method'] = 'get';
1109
  $form['#attributes'] = array('class' => array('search-filter'));
1110
  $form['identifier'] = array(
1111
    '#type' => 'textfield',
1112
    '#title' => t('Identifier'),
1113
    '#default_value' => $filter_presets['identifier'],
1114
    '#size' => 20,
1115
    '#maxlength' => 128
1116
  );
1117
  $form['taxon_name'] = array(
1118
    '#type' => 'textfield',
1119
    '#title' => t('Scientific name'),
1120
    '#default_value' => $filter_presets['taxon_name'],
1121
    '#size' => 20,
1122
    '#maxlength' => 128
1123
  );
1124
  $form['reference_citation'] = array(
1125
    '#type' => 'textfield',
1126
    '#title' => t('Publication'),
1127
    '#default_value' => $filter_presets['reference_citation'],
1128
    '#size' => 20,
1129
    '#maxlength' => 128
1130
  );
1131
  $form['type_designation_status_uuids'] = array(
1132
    '#type' => 'select',
1133
    '#title' => t('Type designation status'),
1134
    '#multiple' => true,
1135
    '#options' => cdm_type_designation_status_filter_terms_as_options('- none -'),
1136
    '#default_value' => $filter_presets['type_designation_status_uuids'],
1137
    "#description" => '<i>' . t('Ctrl + Click to unselect') . '</i>'
1138
  );
1139

    
1140
  $form['submit'] = array(
1141
    '#type' => 'submit',
1142
    '#attributes' => array('class' => array('fa-icon'), 'title' => t('Search')),
1143
    '#value' => decode_entities('&#xf002;'), // fontawesome search icon
1144
//    '#prefix' => "<div class=\"form-item\"><label>&nbsp</label>",
1145
//    '#suffix' => "</div>"
1146

    
1147
  );
1148
  return $form;
1149
}
1150

    
1151

    
1152
function cdm_dataportal_search_registration_taxongraph_form($form, &$form_state) {
1153

    
1154
  static $filter_presets_empty = array(
1155
    'taxon_name'=> null
1156
  );
1157

    
1158
  _add_font_awesome_font();
1159

    
1160
  if(isset($_REQUEST['q']) && $_REQUEST['q']  == 'cdm_dataportal/registration-search/taxongraph'){
1161
    // read the $request_params only if it was send from this form
1162
    $request_params = remove_drupal_form_params($_REQUEST);
1163
  } else {
1164
    $request_params = array();
1165
  }
1166
  $filter_presets = (isset($_SESSION['cdm'][SESSION_KEY_SEARCH_TAXONGRAPH_FOR_REGISTRATION_FILTER]) ? $_SESSION['cdm'][SESSION_KEY_SEARCH_TAXONGRAPH_FOR_REGISTRATION_FILTER] : array());
1167
  $filter_presets = array_merge($filter_presets_empty, $filter_presets, $request_params);
1168

    
1169
  $form['#action'] =  url('/cdm_dataportal/registration-search/taxongraph');
1170
  $form['#method'] = 'get';
1171
  $form['#attributes'] = array('class' => array('search-filter'));
1172
  $form['taxon_name'] = array(
1173
    '#type' => 'textfield',
1174
    '#title' => t('Scientific name'),
1175
    '#default_value' => $filter_presets['taxon_name'],
1176
    '#size' => 20,
1177
    '#maxlength' => 128
1178
  );
1179

    
1180
  $form['submit'] = array(
1181
    '#type' => 'submit',
1182
    '#attributes' => array('class' => array('fa-icon'), 'title' => t('Search')),
1183
    '#value' => decode_entities('&#xf002;'), // fontawesome search icon
1184
//    '#prefix' => "<div class=\"form-item\"><label>&nbsp</label>",
1185
//    '#suffix' => "</div>"
1186

    
1187
  );
1188
  return $form;
1189
}
1190

    
1191
/**
1192
 * Compose the result set of a registration search from a pager object
1193
 *
1194
 * @param $registration_pager
1195
 *    The pager containing registration objects
1196
 *
1197
 * @return
1198
 *   A drupal render array.
1199
 *
1200
 * @ingroup compose
1201
 *
1202
 * TODO compose function into search.inc ?
1203
 */
1204
function compose_registrations_search_results($registration_pager){
1205

    
1206
  $render_array = array();
1207
  $render_array['pre'] = markup_to_render_array("<div class=\"cdm-item-list\">");
1208

    
1209
  if($registration_pager != null && count($registration_pager->records) > 0){
1210
    $items_render_array = array();
1211
    foreach($registration_pager->records as $registration_dto) {
1212

    
1213
      $items_render_array[]  = array(
1214
        '#prefix' => "<div class=\"item\"><div class=\"" . html_class_attribute_ref(new TypedEntityReference("Registration", $registration_dto->uuid)) . "\">",
1215
         'item_data' => compose_registration_dto_compact($registration_dto, 'item-style', 'div'),
1216
        '#suffix' => "</div></div>"
1217
        );
1218
      ;
1219
    }
1220

    
1221
    $render_array['items'] = $items_render_array;
1222
    $render_array['pager'] =  markup_to_render_array(theme('cdm_pager', array(
1223
          'pager' => $registration_pager,
1224
          'path' => $_REQUEST['q'], // stay on same page
1225
          'parameters' => $_REQUEST,
1226
        )));
1227

    
1228
  } else {
1229
    if($registration_pager != null && $registration_pager->count > 0 && count($registration_pager->records) == 0){
1230
      $render_array['items'] = markup_to_render_array("<div id=\"no_results\">Result page out of range.</div>");
1231
    } else {
1232
      $render_array['items'] = markup_to_render_array("<div id=\"no_results\">No results found.</div>");
1233
    }
1234
  }
1235
  $render_array['post'] = markup_to_render_array("</div>");
1236

    
1237
  return $render_array;
1238

    
1239
}
(11-11/18)