Projet

Général

Profil

Paste
Télécharger (26,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / shs / shs.module @ 87dbc3bf

1
<?php
2

    
3
/**
4
 * @file
5
 * Provides an additional widget for term fields to create hierarchical selects.
6
 */
7

    
8
/**
9
 * Implements hook_menu().
10
 */
11
function shs_menu() {
12
  $items = array();
13

    
14
  // Create menu item for JSON callbacks.
15
  $items['js/shs'] = array(
16
    'title' => 'JSON callback',
17
    'description' => 'JSON callbacks for Simple hierarchical select',
18
    'page callback' => 'shs_json',
19
    'access callback' => 'user_access',
20
    'access arguments' => array('access content'),
21
    'type' => MENU_CALLBACK,
22
  );
23

    
24
  return $items;
25
}
26

    
27
/**
28
 * Implements hook_js().
29
 */
30
function shs_js() {
31
  return array(
32
    'json' => array(
33
      'callback' => 'shs_json',
34
      'access callback'  => 'user_access',
35
      'access arguments' => array('access content'),
36
      'dependencies' => array('taxonomy'),
37
    ),
38
  );
39
}
40

    
41
/**
42
 * Menu callback to get data in JSON format.
43
 */
44
function shs_json() {
45
  $result = array(
46
    'success' => FALSE,
47
    'data' => array(),
48
  );
49
  if (isset($_POST['callback'])) {
50
    // Get name of function we need to call to get the data.
51
    $_callback = check_plain($_POST['callback']);
52
    // Is this a valid callback?
53
    $valid_callbacks = shs_json_callbacks();
54
    if (isset($valid_callbacks[$_callback]) && !empty($valid_callbacks[$_callback]['callback']) && function_exists($valid_callbacks[$_callback]['callback'])) {
55
      // Get arguments and validate them.
56
      $post_args = (isset($_POST['arguments']) && is_array($_POST['arguments'])) ? $_POST['arguments'] : array();
57
      $arguments = _shs_json_callback_get_arguments($valid_callbacks[$_callback], $post_args);
58
      if (($callback_result = call_user_func_array($valid_callbacks[$_callback]['callback'], $arguments)) !== FALSE) {
59
        $result['success'] = TRUE;
60
        $result['data'] = $callback_result;
61
      }
62
    }
63
  }
64
  // Return result as JSON string.
65
  drupal_json_output($result);
66
}
67

    
68
/**
69
 * Get a list of supported JSON callbacks.
70
 *
71
 * @return <array>
72
 *   List of valid callbacks with the following structure:
73
 *   - [name of callback]
74
 *     - 'callback': function to call
75
 *     - 'arguments'
76
 *       - [name of argument]: [validation function] (FALSE for no validation)
77
 */
78
function shs_json_callbacks() {
79
  $callbacks = array(
80
    'shs_json_term_get_children' => array(
81
      'callback' => 'shs_json_term_get_children',
82
      'arguments' => array(
83
        'vid' => 'is_numeric',
84
        'parent' => 'is_array',
85
        'settings' => 'is_array',
86
      ),
87
    ),
88
    'shs_json_term_add' => array(
89
      'callback' => 'shs_json_term_add',
90
      'arguments' => array(
91
        'vid' => 'is_numeric',
92
        'parent' => 'is_numeric',
93
        'name' => 'is_string',
94
      ),
95
    ),
96
  );
97
  // Let other modules add some more callbacks and alter the existing.
98
  drupal_alter('shs_json_callbacks', $callbacks);
99
  return $callbacks;
100
}
101

    
102
/**
103
 * Helper function to get the (validated) arguments for a JSON callback.
104
 *
105
 * @param <array> $callback
106
 *   Callback definition from campus_events_json_callbacks().
107
 * @param <array> $arguments
108
 *   Unfiltered arguments posted with $.ajax().
109
 *
110
 * @return <array>
111
 *   List of (validated) arguments for this callback. Any arguments not defined
112
 *   for this callback will be removed.
113
 */
114
function _shs_json_callback_get_arguments($callback, $arguments) {
115
  $result = array();
116
  // Get arguments from callback definition.
117
  $callback_arguments = $callback['arguments'];
118
  foreach ($arguments as $key => $value) {
119
    if (isset($callback_arguments[$key])) {
120
      $argument_valid = TRUE;
121
      if ((($validation_function = $callback_arguments[$key]) !== FALSE) && function_exists($validation_function)) {
122
        // Validate argument.
123
        $argument_valid = $validation_function($value);
124
      }
125
      if ($argument_valid) {
126
        // Add argument and its value to the result list.
127
        $result[$key] = $value;
128
      }
129
    }
130
  }
131
  return $result;
132
}
133

    
134
/**
135
 * Implements hook_views_data_alter().
136
 */
137
function shs_views_data_alter(&$data) {
138
  // Get a list of all field instances with widget type "taxonomy_shs".
139
  $instances = _shs_get_instances('node');
140
  foreach ($instances as $field_instances) {
141
    foreach ($field_instances as $field_name => $instance) {
142
      // Replace filter handler for this field.
143
      if (!empty($data["field_data_{$field_name}"]["{$field_name}_tid"]['filter']['handler'])) {
144
        $data["field_data_{$field_name}"]["{$field_name}_tid"]['filter']['handler'] = 'shs_handler_filter_term_node_tid';
145
      }
146
    }
147
  }
148

    
149
  // Add filter handler for term ID with depth.
150
  $data['node']['shs_term_node_tid_depth'] = array(
151
    'help' => t('Display content if it has the selected taxonomy terms, or children of the selected terms. Due to additional complexity, this has fewer options than the versions without depth. Optionally the filter will use a simple hierarchical select for the selection of terms.'),
152
    'real field' => 'nid',
153
    'filter' => array(
154
      'title' => t('Has taxonomy terms (with depth; %type)', array('%type' => 'Simple hierarchical select')),
155
      'handler' => 'shs_handler_filter_term_node_tid_depth',
156
    ),
157
  );
158
}
159

    
160
/**
161
 * Implements hook_field_widget_info().
162
 */
163
function shs_field_widget_info() {
164
  return array(
165
    'taxonomy_shs' => array(
166
      'label' => t('Simple hierarchical select'),
167
      'field types' => array('taxonomy_term_reference'),
168
      'settings' => array(
169
        'shs' => array(
170
          'node_count' => FALSE,
171
          'create_new_terms' => FALSE,
172
          'create_new_levels' => FALSE,
173
          'force_deepest' => FALSE,
174
        ),
175
      ),
176
    ),
177
  );
178
}
179

    
180
/**
181
 * Implements hook__field_widget_settings_form().
182
 */
183
function shs_field_widget_settings_form($field, $instance) {
184
  $widget = $instance['widget'];
185
  $settings = $widget['settings'];
186

    
187
  $form = array();
188

    
189
  $form['shs'] = array(
190
    '#type' => 'fieldset',
191
    '#title' => 'Simple hierarchical select settings',
192
    '#collapsible' => TRUE,
193
    '#collapsed' => FALSE,
194
    '#tree' => TRUE,
195
  );
196
  $form['shs']['node_count'] = array(
197
    '#type' => 'checkbox',
198
    '#title' => t('Display number of nodes'),
199
    '#description' => t('Display the number of nodes associated with the term.'),
200
    '#default_value' => empty($settings['shs']['node_count']) ? FALSE : $settings['shs']['node_count'],
201
  );
202
  $form['shs']['create_new_terms'] = array(
203
    '#type' => 'checkbox',
204
    '#title' => t('Allow creating new terms'),
205
    '#description' => t('If checked the user will be able to create new terms (permission to edit terms in this vocabulary must be set).'),
206
    '#default_value' => empty($settings['shs']['create_new_terms']) ? FALSE : $settings['shs']['create_new_terms'],
207
  );
208
  $form['shs']['create_new_levels'] = array(
209
    '#type' => 'checkbox',
210
    '#title' => t('Allow creating new levels'),
211
    '#description' => t('If checked the user will be able to create new children for items which do not have any children yet (permission to edit terms in this vocabulary must be set).'),
212
    '#default_value' => empty($settings['shs']['create_new_levels']) ? FALSE : $settings['shs']['create_new_levels'],
213
    '#states' => array(
214
      'visible' => array(
215
        ':input[name="instance[widget][settings][shs][create_new_terms]"]' => array('checked' => TRUE),
216
      ),
217
    ),
218
  );
219
  $form['shs']['force_deepest'] = array(
220
    '#type' => 'checkbox',
221
    '#title' => t('Force selection of deepest level'),
222
    '#description' => t('If checked the user will be forced to select terms from the deepest level.'),
223
    '#default_value' => empty($settings['shs']['force_deepest']) ? FALSE : $settings['shs']['force_deepest'],
224
  );
225

    
226
  // "Chosen" integration.
227
  if (module_exists('chosen')) {
228
    $form['shs']['use_chosen'] = array(
229
      '#type' => 'select',
230
      '#title' => t('Output this field with !chosen', array('!chosen' => l(t('Chosen'), 'http://drupal.org/project/chosen'))),
231
      '#description' => t('Select in which cases the element will use the !chosen module for the term selection of each level.', array('!chosen' => l(t('Chosen'), 'http://drupal.org/project/chosen'))),
232
      '#default_value' => empty($settings['shs']['use_chosen']) ? 'chosen' : $settings['shs']['use_chosen'],
233
      '#options' => array(
234
        'chosen' => t('let chosen decide'),
235
        'always' => t('always'),
236
        'never' => t('never'),
237
      ),
238
    );
239
  }
240

    
241
  return $form;
242
}
243

    
244
/**
245
 * Implements hook_field_widget_error().
246
 */
247
function shs_field_widget_error($element, $error, $form, &$form_state) {
248
  form_error($element, $error['message']);
249
}
250

    
251
/**
252
 * Implements hook_field_widget_form().
253
 */
254
function shs_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
255
  // Get value.
256
  $element_value = NULL;
257
  if (!empty($items[$delta]['tid'])) {
258
    // Use saved value from database or cache.
259
    $element_value = $items[$delta]['tid'];
260
  }
261
  elseif (!empty($form_state['values'][$element['#field_name']][$element['#language']][$delta]['tid'])) {
262
    // Use value from form_state (for example for fields with cardinality = -1).
263
    $element_value = $form_state['values'][$element['#field_name']][$element['#language']][$delta]['tid'];
264
  }
265

    
266
  // Get vocabulary.
267
  $allowed_values = reset($field['settings']['allowed_values']);
268
  if (empty($allowed_values['vocabulary']) || ($vocabulary = taxonomy_vocabulary_machine_name_load($allowed_values['vocabulary'])) === FALSE) {
269
    // No vocabulary selected yet or vocabulary not found.
270
    return array();
271
  }
272

    
273
  // Check if term exists (may be deleted).
274
  if ($element_value && (($term = taxonomy_term_load($element_value)) === FALSE)) {
275
    $element_value = 0;
276
  }
277

    
278
  if (!user_access('edit terms in ' . $vocabulary->vid)) {
279
    // Update setting based on permission.
280
    $instance['widget']['settings']['shs']['create_new_terms'] = FALSE;
281
  }
282
  $instance['widget']['settings']['shs']['required'] = $element['#required'];
283

    
284
  // Element is required and there is no initial value.
285
  if (empty($element_value) && $element['#required']) {
286
    // Load list of options.
287
    $options = shs_term_get_children($vocabulary->vid, 0);
288
    if (count($options)) {
289
      // Set element value to first available option.
290
      $option_keys = array_keys($options);
291
      $element_value = reset($option_keys);
292
    }
293
  }
294

    
295
  // Create element.
296
  $element += array(
297
    '#type' => 'textfield',
298
    '#default_value' => empty($element_value) ? NULL : $element_value,
299
    '#attributes' => array(
300
      'class' => array('shs-enabled'),
301
    ),
302
    // Prevent errors with drupal_strlen().
303
    '#maxlength' => NULL,
304
    '#element_validate' => array('shs_field_widget_validate'),
305
    '#after_build' => array('shs_field_widget_afterbuild'),
306
    '#shs_settings' => $instance['widget']['settings']['shs'],
307
    '#shs_vocabulary' => $vocabulary,
308
  );
309

    
310
  return array('tid' => $element);
311
}
312

    
313
/**
314
 * Afterbuild callback for widgets of type "taxonomy_shs".
315
 */
316
function shs_field_widget_afterbuild($element, &$form_state) {
317
  $js_added = &drupal_static(__FUNCTION__ . '_js_added', array());
318
  // Generate a random hash to avoid merging of settings by drupal_add_js.
319
  // This is necessary until http://drupal.org/node/208611 lands for D7.
320
  $js_hash = &drupal_static(__FUNCTION__ . '_js_hash');
321

    
322
  if (empty($js_hash)) {
323
    $js_hash = _shs_create_hash();
324
  }
325

    
326
  $parents = array();
327
  // Get value from element.
328
  if (!empty($form_state['values'][$element['#field_name']][$element['#language']][$element['#delta']]['tid'])) {
329
    // Use value from form_state (for example for fields with cardinality = -1).
330
    $element['#default_value'] = $form_state['values'][$element['#field_name']][$element['#language']][$element['#delta']];
331
  }
332

    
333
  // Add main Javascript behavior and style only once.
334
  if (count($js_added) == 0) {
335
    // Add behavior.
336
    drupal_add_js(drupal_get_path('module', 'shs') . '/js/shs.js');
337
    // Add styles.
338
    drupal_add_css(drupal_get_path('module', 'shs') . '/theme/shs.form.css');
339
  }
340

    
341
  // Create Javascript settings for the element only if it hasn't been added
342
  // before.
343
  if (empty($js_added[$element['#name']][$js_hash])) {
344
    $element_value = $element['#default_value']['tid'];
345

    
346
    if (empty($element_value)) {
347
      // Add fake parent for new items.
348
      $parents[] = array('tid' => 0);
349
    }
350
    else {
351
      $term_parents = taxonomy_get_parents_all($element_value);
352
      foreach ($term_parents as $term) {
353
        // Create term lineage.
354
        $parents[] = array('tid' => $term->tid);
355
      }
356
    }
357

    
358
    // Create settings needed for our js magic.
359
    $settings_js = array(
360
      'shs' => array(
361
        "{$element['#name']}" => array(
362
          $js_hash => array(
363
            'vid' => $element['#shs_vocabulary']->vid,
364
            'settings' => $element['#shs_settings'],
365
            'default_value' => $element['#default_value'],
366
            'parents' => array_reverse($parents),
367
          ),
368
        ),
369
      ),
370
    );
371

    
372
    // Add settings.
373
    drupal_add_js($settings_js, 'setting');
374

    
375
    if (empty($js_added[$element['#name']])) {
376
      $js_added[$element['#name']] = array();
377
    }
378
    $js_added[$element['#name']][$js_hash] = TRUE;
379
  }
380

    
381
  return $element;
382
}
383

    
384
/**
385
 * Validation handler for widgets of type "taxonomy_shs".
386
 */
387
function shs_field_widget_validate($element, &$form_state, $form) {
388
  $field_name = $element['#field_name'];
389
  $field_language = $element['#language'];
390

    
391
  if (empty($form_state['field'][$field_name][$field_language]['instance']['widget'])) {
392
    return;
393
  }
394
  $field = $form_state['field'][$field_name][$field_language];
395
  $instance = $field['instance'];
396
  $settings = empty($instance['widget']['settings']['shs']) ? array() : $instance['widget']['settings']['shs'];
397

    
398
  // Do we want to force the user to select terms from the deepest level?
399
  $force_deepest_level = empty($settings['force_deepest']) ? FALSE : $settings['force_deepest'];
400
  $value = empty($element['#value']) ? 0 : $element['#value'];
401
  if ($force_deepest_level && $value) {
402
    // Get vocabulary.
403
    $allowed_values = reset($field['field']['settings']['allowed_values']);
404
    if (empty($allowed_values['vocabulary']) || ($vocabulary = taxonomy_vocabulary_machine_name_load($allowed_values['vocabulary'])) === FALSE) {
405
      // No vocabulary selected yet or vocabulary not found.
406
      form_error($element, t('Vocabulary %machine_name is configured as source for field %field_name but could not be found.', array('%machine_name' => $allowed_values['vocabulary'], '%field_name' => $field_name)));
407
    }
408
    // Does the selected term has any children?
409
    $children = shs_term_get_children($vocabulary->vid, $value);
410
    if (count($children)) {
411
      form_error($element, t('You need to select a term from the deepest level.'));
412
    }
413
  }
414
}
415

    
416
/**
417
 * Implements hook_field_formatter_info().
418
 */
419
function shs_field_formatter_info() {
420
  return array(
421
    'shs_default' => array(
422
      'label' => t('Simple hierarchy'),
423
      'field types' => array('taxonomy_term_reference'),
424
      'settings' => array(
425
        'linked' => FALSE,
426
      ),
427
    ),
428
  );
429
}
430

    
431
/**
432
 * Implements hook_field_formatter_settings_form().
433
 */
434
function shs_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
435
  $display = $instance['display'][$view_mode];
436
  $settings = $display['settings'];
437

    
438
  $element = array();
439

    
440
  if ($display['type'] == 'shs_default') {
441
    $element['linked'] = array(
442
      '#title' => t('Link to term page'),
443
      '#type' => 'checkbox',
444
      '#default_value' => $settings['linked'],
445
    );
446
  }
447

    
448
  return $element;
449
}
450

    
451
/**
452
 * Implements hook_field_formatter_settings_summary().
453
 */
454
function shs_field_formatter_settings_summary($field, $instance, $view_mode) {
455
  $display = $instance['display'][$view_mode];
456
  $settings = $display['settings'];
457

    
458
  $summary = '';
459

    
460
  if ($display['type'] == 'shs_default') {
461
    $summary = t('Linked to term page: !linked', array('!linked' => $settings['linked'] ? t('Yes') : t('No')));
462
  }
463

    
464
  return $summary;
465
}
466

    
467
/**
468
 * Implements hook_field_formatter_prepare_view().
469
 */
470
function shs_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
471
  foreach ($entities as $entity_id => $entity) {
472
    foreach ($items[$entity_id] as $delta => $item) {
473
      $items[$entity_id][$delta]['parents'] = array();
474
      // Load list of parent terms.
475
      $parents = taxonomy_get_parents_all($item['tid']);
476
      // Remove current term from list.
477
      array_shift($parents);
478
      foreach (array_reverse($parents) as $parent) {
479
        $items[$entity_id][$delta]['parents'][$parent->tid] = $parent;
480
      }
481
      // Load term.
482
      $items[$entity_id][$delta]['term'] = taxonomy_term_load($item['tid']);
483
    }
484
  }
485
}
486

    
487
/**
488
 * Implements hook_field_formatter_view().
489
 */
490
function shs_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
491
  $elements = array();
492
  $settings = $display['settings'];
493

    
494
  switch ($display['type']) {
495
    case 'shs_default':
496
      foreach ($items as $delta => $item) {
497
        if (empty($item['tid'])) {
498
          continue;
499
        }
500
        $list_items = array();
501
        // Add parent term names.
502
        foreach ($item['parents'] as $parent) {
503
          $list_items[] = array(
504
            'data' => $settings['linked'] ? l($parent->name, "taxonomy/term/{$parent->tid}") : $parent->name,
505
            'class' => array('shs-parent'),
506
          );
507
        };
508
        // Add name of selected term.
509
        $list_items[] = array(
510
          'data' => $settings['linked'] ? l($item['term']->name, "taxonomy/term/{$item['term']->tid}") : $item['term']->name,
511
          'class' => array('shs-term-selected'),
512
        );
513
        $elements[$delta] = array(
514
          '#items' => $list_items,
515
          '#theme' => 'item_list',
516
          '#attributes' => array(
517
            'class' => 'shs-hierarchy',
518
          ),
519
        );
520
      }
521

    
522
      // Add basic style.
523
      $elements['#attached']['css'][] = drupal_get_path('module', 'shs') . '/theme/shs.formatter.css';
524
      break;
525
  }
526

    
527
  return $elements;
528
}
529

    
530
/**
531
 * Function to get the list of children of a term.
532
 *
533
 * The structure is stored in the database cache as well as in drupal_static().
534
 * Cache has the following structure:
535
 * <code>
536
 *   [$parent] => array(
537
 *     [0] => array(tid1, tid2, tid3), // !$node_count.
538
 *     [1] => array('tid1 (x)', 'tid2 (x)', 'tid3 (x)'), // $node_count.
539
 *   ),
540
 * </code>
541
 *
542
 * @param <int> $vid
543
 *   ID of vocabulary the term is associated to.
544
 * @param <int> $parent
545
 *   ID of parent term.
546
 * @param <array> $settings
547
 *   Additional settings (for example "display node count").
548
 * @param <boolean> $reset
549
 *   If TRUE, rebuild the cache for the given $vid and $parent.
550
 *
551
 * @return <array>
552
 *   List of child terms keyed by term id.
553
 */
554
function shs_term_get_children($vid, $parent = 0, $settings = array(), $reset = FALSE) {
555
  $terms = &drupal_static(__FUNCTION__, array());
556
  $node_count = !empty($settings['node_count']) && variable_get('taxonomy_maintain_index_table', TRUE);
557

    
558
  if ($reset || ($vid && empty($terms[$vid][$parent][$node_count]))) {
559
    // Initialize list.
560
    $terms[$vid][$parent] = array(
561
      0 => array(),
562
      1 => array(),
563
    );
564
    $cache_key = "shs:{$vid}";
565
    // Get cached values.
566
    $cache = cache_get($cache_key);
567
    if ($reset || !$cache || ($cache->expire && time() > $cache->expire) || empty($cache->data[$parent][$node_count])) {
568
      // Cache is empty or data has become outdated or the parent is not cached.
569
      if ($cache) {
570
        // Cache exists and is not yet expired but $parent is missing.
571
        $terms[$vid] = $cache->data;
572
      }
573
      if ($reset) {
574
        $terms[$vid][$parent] = array(
575
          0 => array(),
576
          1 => array(),
577
        );
578
      }
579
      // Get term children (only first level).
580
      $tree = taxonomy_get_tree($vid, $parent, 1);
581
      foreach ($tree as $term) {
582
        $terms[$vid][$parent][0][$term->tid] = $term->name;
583
        if ($node_count) {
584
          // Count nodes associated to this term (and its children).
585
          $num_nodes = _shs_term_get_node_count($term, TRUE);
586
          // Update term label.
587
          $terms[$vid][$parent][1][$term->tid] = t('!term_name (!node_count)', array('!term_name' => $term->name, '!node_count' => $num_nodes));
588
        }
589
      }
590
      // Set cached data.
591
      cache_set($cache_key, $terms[$vid], 'cache', CACHE_PERMANENT);
592
    }
593
    else {
594
      // Use cached data.
595
      $terms[$vid] = $cache->data;
596
    }
597
  }
598
  // Allow other module to modify the list of terms.
599
  $alter_options = array(
600
    'vid' => $vid,
601
    'parent' => $parent,
602
    'settings' => $settings,
603
  );
604
  drupal_alter('shs_term_get_children', $terms, $alter_options);
605

    
606
  return empty($terms[$vid][$parent][$node_count]) ? array() : $terms[$vid][$parent][$node_count];
607
}
608

    
609
/**
610
 * JSON callback to get the list of children of a term.
611
 *
612
 * @param <int> $vid
613
 *   ID of vocabulary the term is associated to.
614
 * @param <int> $parent
615
 *   ID of parent term.
616
 * @param <array> $settings
617
 *   Additional settings (for example "display node count").
618
 *
619
 * @return <array>
620
 *   Associative list of child terms.
621
 *
622
 * @see shs_term_get_children()
623
 */
624
function shs_json_term_get_children($vid, $parent = array(), $settings = array()) {
625
  $scope = $result = array();
626
  foreach ($parent as $tid) {
627
    $scope[] = shs_term_get_children($vid, $tid, $settings);
628
  }
629

    
630
  // Rewrite result set to preserve original sort of terms through JSON request.
631
  foreach ($scope as $terms) {
632
    foreach ($terms as $tid => $label) {
633
      $result[] = array(
634
        'tid' => $tid,
635
        'label' => $label,
636
      );
637
    }
638
  }
639

    
640
  return $result;
641
}
642
/**
643
 * Adds a term with ajax.
644
 *
645
 * @param <int> $vid
646
 *   ID of vocabulary to create the term in.
647
 * @param <int> $parent
648
 *   ID of parent term (0 for top level).
649
 * @param <string> $term_name
650
 *   Name of new term.
651
 *
652
 * @return <mixed>
653
 *   Array with tid and name or FALSE on error.
654
 */
655
function shs_json_term_add($vid, $parent, $term_name) {
656
  if (!user_access('edit terms in ' . $vid)) {
657
    // Sorry, but this user may not add a term to this vocabulary.
658
    return FALSE;
659
  }
660

    
661
  $term = (object) array(
662
    'vid' => $vid,
663
    'parent' => $parent,
664
    'name' => check_plain(filter_xss($term_name)),
665
  );
666
  // Save term.
667
  $status = taxonomy_term_save($term);
668

    
669
  // Return term object or FALSE (in case of errors).
670
  return ($status == SAVED_NEW) ? array('tid' => $term->tid, 'name' => $term->name) : FALSE;
671
}
672

    
673
/**
674
 * Implements hook_hook_taxonomy_term_insert().
675
 */
676
function shs_taxonomy_term_insert($term) {
677
  // Update vocabulary cache for the terms parents.
678
  foreach ($term->parent as $parent) {
679
    shs_term_get_children($term->vid, $parent, array('node_count' => TRUE), TRUE);
680
  }
681
}
682

    
683
/**
684
 * Implements hook_hook_taxonomy_term_update().
685
 */
686
function shs_taxonomy_term_update($term) {
687
  // Update vocabulary cache for the terms parents.
688
  foreach ($term->parent as $parent) {
689
    shs_term_get_children($term->vid, $parent, array('node_count' => TRUE), TRUE);
690
  }
691
}
692

    
693
/**
694
 * Implements hook_form_FORM_ID_alter().
695
 */
696
function shs_form_taxonomy_overview_terms_alter(&$form, &$form_state, $form_id) {
697
  $form['#submit'][] = 'shs_form_taxonomy_overview_terms_submit';
698
}
699

    
700
/**
701
 * Implements hook_hook_taxonomy_term_delete().
702
 */
703
function shs_form_taxonomy_overview_terms_submit(&$form, &$form_state) {
704
  // Update vocabulary cache for the terms parents.
705
  shs_term_get_children($form_state['complete form']['#vocabulary']->vid, 0, array('node_count' => TRUE), TRUE);
706
}
707

    
708
/**
709
 * Implements hook_form_FORM_ID_alter().
710
 */
711
function shs_form_taxonomy_form_term_alter(&$form, &$form_state, $form_id) {
712
  if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) {
713
    // Add custom submit handler to update cache.
714
    array_unshift($form['#submit'], 'shs_form_taxonomy_form_term_submit');
715
  }
716
}
717

    
718
/**
719
 * Implements hook_hook_taxonomy_term_delete().
720
 */
721
function shs_form_taxonomy_form_term_submit(&$form, &$form_state) {
722
  // Update vocabulary cache for the terms parents.
723
  $parents = db_select('taxonomy_term_hierarchy', 'tth')
724
          ->fields('tth', array('parent'))
725
          ->condition('tid', $form_state['term']->tid)
726
          ->execute()
727
          ->fetchAll();
728
  if ($parents) {
729
    // Update vocabulary cache for the terms parents.
730
    foreach ($parents as $parent) {
731
      shs_term_get_children($form_state['term']->vid, $parent->parent, array('node_count' => TRUE), TRUE);
732
    }
733
  }
734
}
735

    
736
/**
737
 * Helper function to get all instances of widgets with type "taxonomy_shs".
738
 *
739
 * @param <string> $entity_type
740
 *   Name of entity type.
741
 * @param <string> $bundle
742
 *   Name of bundle (optional).
743
 *
744
 * @return <array>
745
 *   List of instances keyed by field name.
746
 */
747
function _shs_get_instances($entity_type, $bundle = NULL) {
748
  $instances = array();
749
  $field_instances = field_info_instances($entity_type, $bundle);
750
  // Get all field instances with widget type "shs_taxonomy".
751
  if (empty($bundle)) {
752
    foreach ($field_instances as $bundle_name => $bundle_instances) {
753
      foreach ($bundle_instances as $instance) {
754
        if ($instance['widget']['type'] == 'taxonomy_shs') {
755
          $instances[$bundle_name][$instance['field_name']] = $instance;
756
        }
757
      }
758
    }
759
  }
760
  else {
761
    foreach ($field_instances as $instance) {
762
      if ($instance['widget']['type'] == 'taxonomy_shs') {
763
        $instances[$instance['field_name']] = $instance;
764
      }
765
    }
766
  }
767
  return $instances;
768
}
769

    
770
/**
771
 * Helper function to count number of nodes associated to a term.
772
 *
773
 * @param <object> $term
774
 *   The term object.
775
 * @param <boolean> $count_children
776
 *   If set to TRUE, nodes in child terms are counted also.
777
 *
778
 * @return <int>
779
 *   Number of nodes within the term.
780
 */
781
function _shs_term_get_node_count($term, $count_children = FALSE) {
782
  $num_nodes = &drupal_static(__FUNCTION__, array());
783

    
784
  // Maybe this needs some more caching and value-updates on node_save()/
785
  // _update()/delete().
786
  if (empty($num_nodes["{$term->tid}:{$count_children}"])) {
787
    // Count nodes associated to this term.
788
    $num_nodes["{$term->tid}:{$count_children}"] = db_select('taxonomy_index', 'ti')
789
            ->fields('ti')
790
            ->condition('tid', $term->tid)
791
            ->execute()
792
            ->rowCount();
793

    
794
    if ($count_children) {
795
      $tids = array();
796
      $tree = taxonomy_get_tree($term->vid, $term->tid);
797
      foreach ($tree as $child_term) {
798
        $tids[] = $child_term->tid;
799
      }
800
      if (count($tids)) {
801
        $num_nodes["{$term->tid}:{$count_children}"] += db_select('taxonomy_index', 'ti')
802
                ->fields('ti')
803
                ->condition('tid', $tids, 'IN')
804
                ->execute()
805
                ->rowCount();
806
      }
807
    }
808
  }
809

    
810
  return isset($num_nodes["{$term->tid}:{$count_children}"]) ? $num_nodes["{$term->tid}:{$count_children}"] : 0;
811
}
812

    
813
/**
814
 * Helper function to create a pseudo hash needed for javascript settings.
815
 *
816
 * @param <int> $length
817
 *   Lenght of string to return.
818
 *
819
 * @return <string>
820
 *   Random string.
821
 *
822
 * @see DrupalTestCase::randomName()
823
 */
824
function _shs_create_hash($length = 8) {
825
  $values = array_merge(range(65, 90), range(97, 122), range(48, 57));
826
  $max = count($values) - 1;
827
  $hash = chr(mt_rand(97, 122));
828
  for ($i = 1; $i < $length; $i++) {
829
    $hash .= chr($values[mt_rand(0, $max)]);
830
  }
831
  return $hash;
832
}