Projet

Général

Profil

Paste
Télécharger (43,3 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / entityreference / entityreference.module @ a45e4bc1

1 85ad3d82 Assos Assos
<?php
2
3
/**
4
 * Implements hook_ctools_plugin_directory().
5
 */
6
function entityreference_ctools_plugin_directory($module, $plugin) {
7
  if ($module == 'entityreference') {
8
    return 'plugins/' . $plugin;
9
  }
10
}
11
12
/**
13
 * Implements hook_init().
14
 */
15
function entityreference_init() {
16
  // Include feeds.module integration.
17
  if (module_exists('feeds')) {
18
    module_load_include('inc', 'entityreference', 'entityreference.feeds');
19
  }
20
}
21
22
/**
23
 * Implements hook_ctools_plugin_type().
24
 */
25
function entityreference_ctools_plugin_type() {
26
  $plugins['selection'] = array(
27
    'classes' => array('class'),
28
  );
29
  $plugins['behavior'] = array(
30
    'classes' => array('class'),
31
    'process' => 'entityreference_behavior_plugin_process',
32
  );
33
  return $plugins;
34
}
35
36
/**
37
 * CTools callback; Process the behavoir plugins.
38
 */
39
function entityreference_behavior_plugin_process(&$plugin, $info) {
40
  $plugin += array(
41
    'description' => '',
42
    'behavior type' => 'field',
43
    'access callback' => FALSE,
44
    'force enabled' => FALSE,
45
  );
46
}
47
48
/**
49
 * Implements hook_field_info().
50
 */
51
function entityreference_field_info() {
52
  $field_info['entityreference'] = array(
53
    'label' => t('Entity Reference'),
54
    'description' => t('This field reference another entity.'),
55
    'settings' => array(
56
      // Default to the core target entity type node.
57
      'target_type' => 'node',
58
      // The handler for this field.
59
      'handler' => 'base',
60
      // The handler settings.
61
      'handler_settings' => array(),
62
    ),
63
    'instance_settings' => array(),
64
    'default_widget' => 'entityreference_autocomplete',
65
    'default_formatter' => 'entityreference_label',
66
    'property_callbacks' => array('entityreference_field_property_callback'),
67
  );
68
  return $field_info;
69
}
70
71
/**
72
 * Implements hook_flush_caches().
73
 */
74
function entityreference_flush_caches() {
75
  // Because of the intricacies of the info hooks, we are forced to keep a
76
  // separate list of the base tables of each entities, so that we can use
77
  // it in entityreference_field_schema() without calling entity_get_info().
78
  // See http://drupal.org/node/1416558 for details.
79
  $base_tables = array();
80
  foreach (entity_get_info() as $entity_type => $entity_info) {
81
    if (!empty($entity_info['base table']) && !empty($entity_info['entity keys']['id'])) {
82
      $base_tables[$entity_type] = array($entity_info['base table'], $entity_info['entity keys']['id']);
83
    }
84
  }
85
  // We are using a variable because cache is going to be cleared right after
86
  // hook_flush_caches() is finished.
87
  variable_set('entityreference:base-tables', $base_tables);
88
}
89
90
/**
91
 * Implements hook_menu().
92
 */
93
function entityreference_menu() {
94
  $items = array();
95
96
  $items['entityreference/autocomplete/single/%/%/%'] = array(
97
    'title' => 'Entity Reference Autocomplete',
98
    'page callback' => 'entityreference_autocomplete_callback',
99
    'page arguments' => array(2, 3, 4, 5),
100
    'access callback' => 'entityreference_autocomplete_access_callback',
101
    'access arguments' => array(2, 3, 4, 5),
102
    'type' => MENU_CALLBACK,
103
  );
104
  $items['entityreference/autocomplete/tags/%/%/%'] = array(
105
    'title' => 'Entity Reference Autocomplete',
106
    'page callback' => 'entityreference_autocomplete_callback',
107
    'page arguments' => array(2, 3, 4, 5),
108
    'access callback' => 'entityreference_autocomplete_access_callback',
109
    'access arguments' => array(2, 3, 4, 5),
110
    'type' => MENU_CALLBACK,
111
  );
112
113
  return $items;
114
}
115
116
/**
117
 * Implements hook_field_is_empty().
118
 */
119
function entityreference_field_is_empty($item, $field) {
120
  $empty = !isset($item['target_id']) || !is_numeric($item['target_id']);
121
122
  // Invoke the behaviors to allow them to override the empty status.
123
  foreach (entityreference_get_behavior_handlers($field) as $handler) {
124
    $handler->is_empty_alter($empty, $item, $field);
125
  }
126
  return $empty;
127
}
128
129
/**
130
 * Get the behavior handlers for a given entityreference field.
131
 */
132
function entityreference_get_behavior_handlers($field, $instance = NULL) {
133
  $object_cache = drupal_static(__FUNCTION__);
134
  $identifier = $field['field_name'];
135
  if (!empty($instance)) {
136
    $identifier .= ':' . $instance['entity_type'] . ':' . $instance['bundle'];
137
  }
138
139
  if (!isset($object_cache[$identifier])) {
140
    $object_cache[$identifier] = array();
141
142
    // Merge in defaults.
143
    $field['settings'] += array('behaviors' => array());
144
145
    $object_cache[$field['field_name']] = array();
146
    $behaviors = !empty($field['settings']['handler_settings']['behaviors']) ? $field['settings']['handler_settings']['behaviors'] : array();
147
    if (!empty($instance['settings']['behaviors'])) {
148
      $behaviors = array_merge($behaviors, $instance['settings']['behaviors']);
149
    }
150
    foreach ($behaviors as $behavior => $settings) {
151
      if (empty($settings['status'])) {
152
        // Behavior is not enabled.
153
        continue;
154
      }
155
156
      $object_cache[$identifier][] = _entityreference_get_behavior_handler($behavior);
157
    }
158
  }
159
160
  return $object_cache[$identifier];
161
}
162
163
/**
164
 * Get the behavior handler for a given entityreference field and instance.
165
 *
166
 * @param $handler
167
 *   The behavior handler name.
168
 */
169
function _entityreference_get_behavior_handler($behavior) {
170
  $object_cache = drupal_static(__FUNCTION__);
171
172
  if (!isset($object_cache[$behavior])) {
173
    ctools_include('plugins');
174
    $class = ctools_plugin_load_class('entityreference', 'behavior', $behavior, 'class');
175
176
    $class = class_exists($class) ? $class : 'EntityReference_BehaviorHandler_Broken';
177
    $object_cache[$behavior] = new $class($behavior);
178
  }
179
180
  return $object_cache[$behavior];
181
}
182
183
/**
184
 * Get the selection handler for a given entityreference field.
185
 */
186
function entityreference_get_selection_handler($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
187
  ctools_include('plugins');
188
  $handler = $field['settings']['handler'];
189
  $class = ctools_plugin_load_class('entityreference', 'selection', $handler, 'class');
190
191
  if (class_exists($class)) {
192
    return call_user_func(array($class, 'getInstance'), $field, $instance, $entity_type, $entity);
193
  }
194
  else {
195
    return EntityReference_SelectionHandler_Broken::getInstance($field, $instance, $entity_type, $entity);
196
  }
197
}
198
199
/**
200
 * Implements hook_field_load().
201
 */
202
function entityreference_field_load($entity_type, $entities, $field, $instances, $langcode, &$items) {
203
  // Invoke the behaviors.
204
  foreach (entityreference_get_behavior_handlers($field) as $handler) {
205
    $handler->load($entity_type, $entities, $field, $instances, $langcode, $items);
206
  }
207
}
208
209
/**
210
 * Implements hook_field_validate().
211
 */
212
function entityreference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
213
  $ids = array();
214
  foreach ($items as $delta => $item) {
215
    if (!entityreference_field_is_empty($item, $field) && $item['target_id'] !== NULL) {
216
      $ids[$item['target_id']] = $delta;
217
    }
218
  }
219
220
  if ($ids) {
221
    $valid_ids = entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->validateReferencableEntities(array_keys($ids));
222
223
    $invalid_entities = array_diff_key($ids, array_flip($valid_ids));
224
    if ($invalid_entities) {
225
      foreach ($invalid_entities as $id => $delta) {
226
        $errors[$field['field_name']][$langcode][$delta][] = array(
227
          'error' => 'entityreference_invalid_entity',
228
          'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)),
229
        );
230
      }
231
    }
232
  }
233
234
  // Invoke the behaviors.
235
  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
236
    $handler->validate($entity_type, $entity, $field, $instance, $langcode, $items, $errors);
237
  }
238
}
239
240
/**
241
 * Implements hook_field_presave().
242
 *
243
 * Adds the target type to the field data structure when saving.
244
 */
245
function entityreference_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
246
  // Invoke the behaviors.
247
  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
248
    $handler->presave($entity_type, $entity, $field, $instance, $langcode, $items);
249
  }
250
}
251
252
/**
253
 * Implements hook_field_insert().
254
 */
255
function entityreference_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
256
  // Invoke the behaviors.
257
  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
258
    $handler->insert($entity_type, $entity, $field, $instance, $langcode, $items);
259
  }
260
}
261
262
/**
263
 * Implements hook_field_attach_insert().
264
 *
265
 * Emulates a post-insert hook.
266
 */
267
function entityreference_field_attach_insert($entity_type, $entity) {
268
  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
269
  foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
270
    $field = field_info_field($field_name);
271
    if ($field['type'] == 'entityreference') {
272
      foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
273
        $handler->postInsert($entity_type, $entity, $field, $instance);
274
      }
275
    }
276
  }
277
}
278
279
/**
280
 * Implements hook_field_update().
281
 */
282
function entityreference_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
283
  // Invoke the behaviors.
284
  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
285
    $handler->update($entity_type, $entity, $field, $instance, $langcode, $items);
286
  }
287
}
288
289
/**
290
 * Implements hook_field_attach_update().
291
 *
292
 * Emulates a post-update hook.
293
 */
294
function entityreference_field_attach_update($entity_type, $entity) {
295
  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
296
  foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
297
    $field = field_info_field($field_name);
298
    if ($field['type'] == 'entityreference') {
299
      foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
300
        $handler->postUpdate($entity_type, $entity, $field, $instance);
301
      }
302
    }
303
  }
304
}
305
306
/**
307
 * Implements hook_field_delete().
308
 */
309
function entityreference_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {
310
  // Invoke the behaviors.
311
  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
312
    $handler->delete($entity_type, $entity, $field, $instance, $langcode, $items);
313
  }
314
}
315
316
/**
317
 * Implements hook_field_attach_delete().
318
 *
319
 * Emulates a post-delete hook.
320
 */
321
function entityreference_field_attach_delete($entity_type, $entity) {
322
  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
323
  foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
324
    $field = field_info_field($field_name);
325
    if ($field['type'] == 'entityreference') {
326
      foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
327
        $handler->postDelete($entity_type, $entity, $field, $instance);
328
      }
329
    }
330
  }
331
}
332
333
/**
334
 * Implements hook_entity_insert().
335
 */
336
function entityreference_entity_insert($entity, $entity_type) {
337
  entityreference_entity_crud($entity, $entity_type, 'entityPostInsert');
338
}
339
340
/**
341
 * Implements hook_entity_update().
342
 */
343
function entityreference_entity_update($entity, $entity_type) {
344
  entityreference_entity_crud($entity, $entity_type, 'entityPostUpdate');
345
}
346
347
/**
348
 * Implements hook_entity_delete().
349
 */
350
function entityreference_entity_delete($entity, $entity_type) {
351
  entityreference_entity_crud($entity, $entity_type, 'entityPostDelete');
352
}
353
354
/**
355
 * Invoke a behavior based on entity CRUD.
356
 *
357
 * @param $entity
358
 *   The entity object.
359
 * @param $entity_type
360
 *   The entity type.
361
 * @param $method_name
362
 *   The method to invoke.
363
 */
364
function entityreference_entity_crud($entity, $entity_type, $method_name) {
365
  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
366
  foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
367
    $field = field_info_field($field_name);
368
    if ($field['type'] == 'entityreference') {
369
      foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
370
        $handler->{$method_name}($entity_type, $entity, $field, $instance);
371
      }
372
    }
373
  }
374
}
375
376
/**
377
 * Implements hook_field_settings_form().
378
 */
379
function entityreference_field_settings_form($field, $instance, $has_data) {
380
  // The field settings infrastructure is not AJAX enabled by default,
381
  // because it doesn't pass over the $form_state.
382
  // Build the whole form into a #process in which we actually have access
383
  // to the form state.
384
  $form = array(
385
    '#type' => 'container',
386
    '#attached' => array(
387
      'css' => array(drupal_get_path('module', 'entityreference') . '/entityreference.admin.css'),
388
    ),
389
    '#process' => array(
390
      '_entityreference_field_settings_process',
391
      '_entityreference_field_settings_ajax_process',
392
    ),
393
    '#element_validate' => array('_entityreference_field_settings_validate'),
394
    '#field' => $field,
395
    '#instance' => $instance,
396
    '#has_data' => $has_data,
397
  );
398
  return $form;
399
}
400
401
function _entityreference_field_settings_process($form, $form_state) {
402
  $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field'];
403
  $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance'];
404
  $has_data = $form['#has_data'];
405
406
  $settings = $field['settings'];
407
  $settings += array('handler' => 'base');
408
409
  // Select the target entity type.
410
  $entity_type_options = array();
411
  foreach (entity_get_info() as $entity_type => $entity_info) {
412
    $entity_type_options[$entity_type] = $entity_info['label'];
413
  }
414
415
  $form['target_type'] = array(
416
    '#type' => 'select',
417
    '#title' => t('Target type'),
418
    '#options' => $entity_type_options,
419
    '#default_value' => $field['settings']['target_type'],
420
    '#required' => TRUE,
421
    '#description' => t('The entity type that can be referenced through this field.'),
422
    '#disabled' => $has_data,
423
    '#size' => 1,
424
    '#ajax' => TRUE,
425
    '#limit_validation_errors' => array(),
426
  );
427
428
  ctools_include('plugins');
429
  $handlers = ctools_get_plugins('entityreference', 'selection');
430
  uasort($handlers, 'ctools_plugin_sort');
431
  $handlers_options = array();
432
  foreach ($handlers as $handler => $handler_info) {
433
    $handlers_options[$handler] = check_plain($handler_info['title']);
434
  }
435
436
  $form['handler'] = array(
437
    '#type' => 'fieldset',
438
    '#title' => t('Entity selection'),
439
    '#tree' => TRUE,
440
    '#process' => array('_entityreference_form_process_merge_parent'),
441
  );
442
443
  $form['handler']['handler'] = array(
444
    '#type' => 'select',
445
    '#title' => t('Mode'),
446
    '#options' => $handlers_options,
447
    '#default_value' => $settings['handler'],
448
    '#required' => TRUE,
449
    '#ajax' => TRUE,
450
    '#limit_validation_errors' => array(),
451
  );
452
  $form['handler_submit'] = array(
453
    '#type' => 'submit',
454
    '#value' => t('Change handler'),
455
    '#limit_validation_errors' => array(),
456
    '#attributes' => array(
457
      'class' => array('js-hide'),
458
    ),
459
    '#submit' => array('entityreference_settings_ajax_submit'),
460
  );
461
462
  $form['handler']['handler_settings'] = array(
463
    '#type' => 'container',
464
    '#attributes' => array('class' => array('entityreference-settings')),
465
  );
466
467
  $handler = entityreference_get_selection_handler($field, $instance);
468
  $form['handler']['handler_settings'] += $handler->settingsForm($field, $instance);
469
470
  _entityreference_get_behavior_elements($form, $field, $instance, 'field');
471
  if (!empty($form['behaviors'])) {
472
    $form['behaviors'] += array(
473
      '#type' => 'fieldset',
474
      '#title' => t('Additional behaviors'),
475
      '#parents' => array_merge($form['#parents'], array('handler_settings', 'behaviors')),
476
    );
477
  }
478
479
  return $form;
480
}
481
482
function _entityreference_field_settings_ajax_process($form, $form_state) {
483
  _entityreference_field_settings_ajax_process_element($form, $form);
484
  return $form;
485
}
486
487
function _entityreference_field_settings_ajax_process_element(&$element, $main_form) {
488
  if (isset($element['#ajax']) && $element['#ajax'] === TRUE) {
489
    $element['#ajax'] = array(
490
      'callback' => 'entityreference_settings_ajax',
491
      'wrapper' => $main_form['#id'],
492
      'element' => $main_form['#array_parents'],
493
    );
494
  }
495
496
  foreach (element_children($element) as $key) {
497
    _entityreference_field_settings_ajax_process_element($element[$key], $main_form);
498
  }
499
}
500
501
function _entityreference_form_process_merge_parent($element) {
502
  $parents = $element['#parents'];
503
  array_pop($parents);
504
  $element['#parents'] = $parents;
505
  return $element;
506
}
507
508
function _entityreference_element_validate_filter(&$element, &$form_state) {
509
  $element['#value'] = array_filter($element['#value']);
510
  form_set_value($element, $element['#value'], $form_state);
511
}
512
513
function _entityreference_field_settings_validate($form, &$form_state) {
514
  // Store the new values in the form state.
515
  $field = $form['#field'];
516
  if (isset($form_state['values']['field'])) {
517
    $field['settings'] = $form_state['values']['field']['settings'];
518
  }
519
  $form_state['entityreference']['field'] = $field;
520
521
  unset($form_state['values']['field']['settings']['handler_submit']);
522
}
523
524
/**
525
 * Implements hook_field_instance_settings_form().
526
 */
527
function entityreference_field_instance_settings_form($field, $instance) {
528
  $form['settings'] = array(
529
    '#type' => 'container',
530
    '#attached' => array(
531
      'css' => array(drupal_get_path('module', 'entityreference') . '/entityreference.admin.css'),
532
    ),
533
    '#weight' => 10,
534
    '#tree' => TRUE,
535
    '#process' => array(
536
      '_entityreference_form_process_merge_parent',
537
      '_entityreference_field_instance_settings_form',
538
      '_entityreference_field_settings_ajax_process',
539
    ),
540
    '#element_validate' => array('_entityreference_field_instance_settings_validate'),
541
    '#field' => $field,
542
    '#instance' => $instance,
543
  );
544
545
  return $form;
546
}
547
548
function _entityreference_field_instance_settings_form($form, $form_state) {
549
  $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field'];
550
  $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance'];
551
552
  _entityreference_get_behavior_elements($form, $field, $instance, 'instance');
553
  if (!empty($form['behaviors'])) {
554
    $form['behaviors'] += array(
555
      '#type' => 'fieldset',
556
      '#title' => t('Additional behaviors'),
557
      '#process' => array(
558
        '_entityreference_field_settings_ajax_process',
559
      ),
560
    );
561
  }
562
  return $form;
563
}
564
565
function _entityreference_field_instance_settings_validate($form, &$form_state) {
566
  // Store the new values in the form state.
567
  $instance = $form['#instance'];
568
  if (isset($form_state['values']['instance'])) {
569
    $instance = drupal_array_merge_deep($instance, $form_state['values']['instance']);
570
  }
571
  $form_state['entityreference']['instance'] = $instance;
572
}
573
574
/**
575
 * Get the field or instance elements for the field configuration.
576
 */
577
function _entityreference_get_behavior_elements(&$element, $field, $instance, $level) {
578
  // Add the accessible behavior handlers.
579
  $behavior_plugins = entityreference_get_accessible_behavior_plugins($field, $instance);
580
581
  if ($behavior_plugins[$level]) {
582
    $element['behaviors'] = array();
583
584
    foreach ($behavior_plugins[$level] as $name => $plugin) {
585
      if ($level == 'field') {
586
        $settings = !empty($field['settings']['handler_settings']['behaviors'][$name]) ? $field['settings']['handler_settings']['behaviors'][$name] : array();
587
      }
588
      else {
589
        $settings = !empty($instance['settings']['behaviors'][$name]) ? $instance['settings']['behaviors'][$name] : array();
590
      }
591
      $settings += array('status' => $plugin['force enabled']);
592
593
      // Render the checkbox.
594
      $element['behaviors'][$name] = array(
595
        '#tree' => TRUE,
596
      );
597
      $element['behaviors'][$name]['status'] = array(
598
        '#type' => 'checkbox',
599
        '#title' => check_plain($plugin['title']),
600
        '#description' => $plugin['description'],
601
        '#default_value' => $settings['status'],
602
        '#disabled' => $plugin['force enabled'],
603
        '#ajax' => TRUE,
604
      );
605
606
      if ($settings['status']) {
607
        $handler = _entityreference_get_behavior_handler($name);
608
        if ($behavior_elements = $handler->settingsForm($field, $instance)) {
609
          foreach ($behavior_elements as $key => &$behavior_element) {
610
            $behavior_element += array(
611
              '#default_value' => !empty($settings[$key]) ? $settings[$key] : NULL,
612
            );
613
          }
614
615
          // Get the behavior settings.
616
          $behavior_elements += array(
617
            '#type' => 'container',
618
            '#process' => array('_entityreference_form_process_merge_parent'),
619
            '#attributes' => array(
620
              'class' => array('entityreference-settings'),
621
            ),
622
          );
623
          $element['behaviors'][$name]['settings'] = $behavior_elements;
624
        }
625
      }
626
    }
627
  }
628
}
629
630
/**
631
 * Get all accessible behavior plugins.
632
 */
633
function entityreference_get_accessible_behavior_plugins($field, $instance) {
634
  ctools_include('plugins');
635
  $plugins = array('field' => array(), 'instance' => array());
636
  foreach (ctools_get_plugins('entityreference', 'behavior') as $name => $plugin) {
637
    $handler = _entityreference_get_behavior_handler($name);
638
    $level = $plugin['behavior type'];
639
    if ($handler->access($field, $instance)) {
640
      $plugins[$level][$name] = $plugin;
641
    }
642
  }
643
  return $plugins;
644
}
645
646
/**
647
 * Ajax callback for the handler settings form.
648
 *
649
 * @see entityreference_field_settings_form()
650
 */
651
function entityreference_settings_ajax($form, $form_state) {
652
  $trigger = $form_state['triggering_element'];
653
  return drupal_array_get_nested_value($form, $trigger['#ajax']['element']);
654
}
655
656
/**
657
 * Submit handler for the non-JS case.
658
 *
659
 * @see entityreference_field_settings_form()
660
 */
661
function entityreference_settings_ajax_submit($form, &$form_state) {
662
  $form_state['rebuild'] = TRUE;
663
}
664
665
/**
666
 * Property callback for the Entity Metadata framework.
667
 */
668
function entityreference_field_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
669
  // Set the property type based on the targe type.
670
  $field_type['property_type'] = $field['settings']['target_type'];
671
672
  // Then apply the default.
673
  entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type);
674
675
  // Invoke the behaviors to allow them to change the properties.
676
  foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) {
677
    $handler->property_info_alter($info, $entity_type, $field, $instance, $field_type);
678
  }
679
}
680
681
/**
682
 * Implements hook_field_widget_info().
683
 */
684
function entityreference_field_widget_info() {
685
  $widgets['entityreference_autocomplete'] = array(
686
    'label' => t('Autocomplete'),
687
    'description' => t('An autocomplete text field.'),
688
    'field types' => array('entityreference'),
689
    'settings' => array(
690
      'match_operator' => 'CONTAINS',
691
      'size' => 60,
692
      // We don't have a default here, because it's not the same between
693
      // the two widgets, and the Field API doesn't update default
694
      // settings when the widget changes.
695
      'path' => '',
696
    ),
697
  );
698
699
  $widgets['entityreference_autocomplete_tags'] = array(
700
    'label' => t('Autocomplete (Tags style)'),
701
    'description' => t('An autocomplete text field.'),
702
    'field types' => array('entityreference'),
703
    'settings' => array(
704
      'match_operator' => 'CONTAINS',
705
      'size' => 60,
706
      // We don't have a default here, because it's not the same between
707
      // the two widgets, and the Field API doesn't update default
708
      // settings when the widget changes.
709
      'path' => '',
710
    ),
711
    'behaviors' => array(
712
      'multiple values' => FIELD_BEHAVIOR_CUSTOM,
713
    ),
714
  );
715
716
  return $widgets;
717
}
718
719
/**
720
 * Implements hook_field_widget_info_alter().
721
 */
722
function entityreference_field_widget_info_alter(&$info) {
723
  if (module_exists('options')) {
724
    $info['options_select']['field types'][] = 'entityreference';
725
    $info['options_buttons']['field types'][] = 'entityreference';
726
  }
727
}
728
729
/**
730
 * Implements hook_field_widget_settings_form().
731
 */
732
function entityreference_field_widget_settings_form($field, $instance) {
733
  $widget = $instance['widget'];
734
  $settings = $widget['settings'] + field_info_widget_settings($widget['type']);
735
736
  $form = array();
737
738
  if ($widget['type'] == 'entityreference_autocomplete' || $widget['type'] == 'entityreference_autocomplete_tags') {
739
    $form['match_operator'] = array(
740
      '#type' => 'select',
741
      '#title' => t('Autocomplete matching'),
742
      '#default_value' => $settings['match_operator'],
743
      '#options' => array(
744
        'STARTS_WITH' => t('Starts with'),
745
        'CONTAINS' => t('Contains'),
746
      ),
747
      '#description' => t('Select the method used to collect autocomplete suggestions. Note that <em>Contains</em> can cause performance issues on sites with thousands of nodes.'),
748
    );
749
    $form['size'] = array(
750
      '#type' => 'textfield',
751
      '#title' => t('Size of textfield'),
752
      '#default_value' => $settings['size'],
753
      '#element_validate' => array('_element_validate_integer_positive'),
754
      '#required' => TRUE,
755
    );
756
  }
757
758
  return $form;
759
}
760
761
/**
762
 * Implements hook_options_list().
763
 */
764
function entityreference_options_list($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
765
  if (!$options = entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->getReferencableEntities()) {
766
    return array();
767
  }
768
769
  // Rebuild the array, by changing the bundle key into the bundle label.
770
  $target_type = $field['settings']['target_type'];
771
  $entity_info = entity_get_info($target_type);
772
773
  $return = array();
774
  foreach ($options as $bundle => $entity_ids) {
775
    $bundle_label = check_plain($entity_info['bundles'][$bundle]['label']);
776
    $return[$bundle_label] = $entity_ids;
777
  }
778
779
  return count($return) == 1 ? reset($return) : $return;
780
}
781
782
/**
783
 * Implements hook_query_TAG_alter().
784
 */
785
function entityreference_query_entityreference_alter(QueryAlterableInterface $query) {
786
  $handler = $query->getMetadata('entityreference_selection_handler');
787
  $handler->entityFieldQueryAlter($query);
788
}
789
790
/**
791
 * Implements hook_field_widget_form().
792
 */
793
function entityreference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
794
  // Ensure that the entity target type exists before displaying the widget.
795
  $entity_info = entity_get_info($field['settings']['target_type']);
796
  if (empty($entity_info)){
797
    return;
798
  }
799
  $entity_type = $instance['entity_type'];
800
  $entity = isset($element['#entity']) ? $element['#entity'] : NULL;
801
  $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity);
802
803
  if ($instance['widget']['type'] == 'entityreference_autocomplete' || $instance['widget']['type'] == 'entityreference_autocomplete_tags') {
804
805
    if ($instance['widget']['type'] == 'entityreference_autocomplete') {
806
      // We let the Field API handles multiple values for us, only take
807
      // care of the one matching our delta.
808
      if (isset($items[$delta])) {
809
        $items = array($items[$delta]);
810
      }
811
      else {
812
        $items = array();
813
      }
814
    }
815
816
    $entity_ids = array();
817
    $entity_labels = array();
818
819
    // Build an array of entities ID.
820
    foreach ($items as $item) {
821
      $entity_ids[] = $item['target_id'];
822
    }
823
824
    // Load those entities and loop through them to extract their labels.
825
    $entities = entity_load($field['settings']['target_type'], $entity_ids);
826
827
    foreach ($entities as $entity_id => $entity_item) {
828
      $label = $handler->getLabel($entity_item);
829
      $key = "$label ($entity_id)";
830
      // Labels containing commas or quotes must be wrapped in quotes.
831
      if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
832
        $key = '"' . str_replace('"', '""', $key) . '"';
833
      }
834
      $entity_labels[] = $key;
835
    }
836
837
    // Prepare the autocomplete path.
838
    if (!empty($instance['widget']['settings']['path'])) {
839
      $autocomplete_path = $instance['widget']['settings']['path'];
840
    }
841
    else {
842
      $autocomplete_path = $instance['widget']['type'] == 'entityreference_autocomplete' ? 'entityreference/autocomplete/single' : 'entityreference/autocomplete/tags';
843
    }
844
845
    $autocomplete_path .= '/' . $field['field_name'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/';
846
    // Use <NULL> as a placeholder in the URL when we don't have an entity.
847
    // Most webservers collapse two consecutive slashes.
848
    $id = 'NULL';
849
    if ($entity) {
850
      list($eid) = entity_extract_ids($entity_type, $entity);
851
      if ($eid) {
852
        $id = $eid;
853
      }
854
    }
855
    $autocomplete_path .= $id;
856
857
    if ($instance['widget']['type'] == 'entityreference_autocomplete') {
858
      $element += array(
859
        '#type' => 'textfield',
860
        '#maxlength' => 1024,
861
        '#default_value' => implode(', ', $entity_labels),
862
        '#autocomplete_path' => $autocomplete_path,
863
        '#size' => $instance['widget']['settings']['size'],
864
        '#element_validate' => array('_entityreference_autocomplete_validate'),
865
      );
866
      return array('target_id' => $element);
867
    }
868
    else {
869
      $element += array(
870
        '#type' => 'textfield',
871
        '#maxlength' => 1024,
872
        '#default_value' => implode(', ', $entity_labels),
873
        '#autocomplete_path' => $autocomplete_path,
874
        '#size' => $instance['widget']['settings']['size'],
875
        '#element_validate' => array('_entityreference_autocomplete_tags_validate'),
876
      );
877
      return $element;
878
    }
879
  }
880
}
881
882
function _entityreference_autocomplete_validate($element, &$form_state, $form) {
883
  // If a value was entered into the autocomplete...
884
  $value = '';
885
  if (!empty($element['#value'])) {
886
    // Take "label (entity id)', match the id from parenthesis.
887
    if (preg_match("/.+\((\d+)\)/", $element['#value'], $matches)) {
888
      $value = $matches[1];
889
    }
890
    else {
891
      // Try to get a match from the input string when the user didn't use the
892
      // autocomplete but filled in a value manually.
893
      $field = field_info_field($element['#field_name']);
894
      $handler = entityreference_get_selection_handler($field);
895
      $field_name = $element['#field_name'];
896
      $field = field_info_field($field_name);
897
      $instance = field_info_instance($element['#entity_type'], $field_name, $element['#bundle']);
898
      $handler = entityreference_get_selection_handler($field, $instance);
899
      $value = $handler->validateAutocompleteInput($element['#value'], $element, $form_state, $form);
900
    }
901
  }
902
  // Update the value of this element so the field can validate the product IDs.
903
  form_set_value($element, $value, $form_state);
904
}
905
906
function _entityreference_autocomplete_tags_validate($element, &$form_state, $form) {
907
  $value = array();
908
  // If a value was entered into the autocomplete...
909
  if (!empty($element['#value'])) {
910
    $entities = drupal_explode_tags($element['#value']);
911
    $value = array();
912
    foreach ($entities as $entity) {
913
      // Take "label (entity id)', match the id from parenthesis.
914
      if (preg_match("/.+\((\d+)\)/", $entity, $matches)) {
915
        $value[] = array(
916
          'target_id' => $matches[1],
917
        );
918
      }
919
      else {
920
        // Try to get a match from the input string when the user didn't use the
921
        // autocomplete but filled in a value manually.
922
        $field = field_info_field($element['#field_name']);
923
        $handler = entityreference_get_selection_handler($field);
924
        $value[] = array(
925
          'target_id' => $handler->validateAutocompleteInput($entity, $element, $form_state, $form),
926
        );
927
      }
928
    }
929
  }
930
  // Update the value of this element so the field can validate the product IDs.
931
  form_set_value($element, $value, $form_state);
932
}
933
934
/**
935
 * Implements hook_field_widget_error().
936
 */
937
function entityreference_field_widget_error($element, $error) {
938
  form_error($element, $error['message']);
939
}
940
941
/**
942
 * Menu Access callback for the autocomplete widget.
943
 *
944
 * @param $type
945
 *   The widget type (i.e. 'single' or 'tags').
946
 * @param $field_name
947
 *   The name of the entity-reference field.
948
 * @param $entity_type
949
 *   The entity type.
950
 * @param $bundle_name
951
 *   The bundle name.
952
 * @return
953
 *   True if user can access this menu item.
954
 */
955
function entityreference_autocomplete_access_callback($type, $field_name, $entity_type, $bundle_name) {
956
  $field = field_info_field($field_name);
957
  $instance = field_info_instance($entity_type, $field_name, $bundle_name);
958
959
  if (!$field || !$instance || $field['type'] != 'entityreference' || !field_access('edit', $field, $entity_type)) {
960
    return FALSE;
961
  }
962
  return TRUE;
963
}
964
965
/**
966
 * Menu callback: autocomplete the label of an entity.
967
 *
968
 * @param $type
969
 *   The widget type (i.e. 'single' or 'tags').
970
 * @param $field_name
971
 *   The name of the entity-reference field.
972
 * @param $entity_type
973
 *   The entity type.
974
 * @param $bundle_name
975
 *   The bundle name.
976
 * @param $entity_id
977
 *   Optional; The entity ID the entity-reference field is attached to.
978
 *   Defaults to ''.
979
 * @param $string
980
 *   The label of the entity to query by.
981
 */
982
function entityreference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $entity_id = '', $string = '') {
983
  // If the request has a '/' in the search text, then the menu system will have
984
  // split it into multiple arguments and $string will only be a partial. We want
985
  //  to make sure we recover the intended $string.
986
  $args = func_get_args();
987
  // Shift off the $type, $field_name, $entity_type, $bundle_name, and $entity_id args.
988
  array_shift($args);
989
  array_shift($args);
990
  array_shift($args);
991
  array_shift($args);
992
  array_shift($args);
993
  $string = implode('/', $args);
994
995
  $field = field_info_field($field_name);
996
  $instance = field_info_instance($entity_type, $field_name, $bundle_name);
997
998
  return entityreference_autocomplete_callback_get_matches($type, $field, $instance, $entity_type, $entity_id, $string);
999
}
1000
1001
/**
1002
 * Return JSON based on given field, instance and string.
1003
 *
1004
 * This function can be used by other modules that wish to pass a mocked
1005
 * definition of the field on instance.
1006
 *
1007
 * @param $type
1008
 *   The widget type (i.e. 'single' or 'tags').
1009
 * @param $field
1010
 *   The field array defintion.
1011
 * @param $instance
1012
 *   The instance array defintion.
1013
 * @param $entity_type
1014
 *   The entity type.
1015
 * @param $entity_id
1016
 *   Optional; The entity ID the entity-reference field is attached to.
1017
 *   Defaults to ''.
1018
 * @param $string
1019
 *   The label of the entity to query by.
1020
 */
1021
function entityreference_autocomplete_callback_get_matches($type, $field, $instance, $entity_type, $entity_id = '', $string = '') {
1022
  $matches = array();
1023
1024
  $entity = NULL;
1025
  if ($entity_id !== 'NULL') {
1026
    $entity = entity_load_single($entity_type, $entity_id);
1027
    $has_view_access = (entity_access('view', $entity_type, $entity) !== FALSE);
1028
    $has_update_access = (entity_access('update', $entity_type, $entity) !== FALSE);
1029
    if (!$entity || !($has_view_access || $has_update_access)) {
1030
      return MENU_ACCESS_DENIED;
1031
    }
1032
  }
1033
1034
  $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity);
1035
1036
  if ($type == 'tags') {
1037
    // The user enters a comma-separated list of tags. We only autocomplete the last tag.
1038
    $tags_typed = drupal_explode_tags($string);
1039
    $tag_last = drupal_strtolower(array_pop($tags_typed));
1040
    if (!empty($tag_last)) {
1041
      $prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : '';
1042
    }
1043
  }
1044
  else {
1045
    // The user enters a single tag.
1046
    $prefix = '';
1047
    $tag_last = $string;
1048
  }
1049
1050
  if (isset($tag_last)) {
1051
    // Get an array of matching entities.
1052
    $entity_labels = $handler->getReferencableEntities($tag_last, $instance['widget']['settings']['match_operator'], 10);
1053
1054
    // Loop through the products and convert them into autocomplete output.
1055
    foreach ($entity_labels as $values) {
1056
      foreach ($values as $entity_id => $label) {
1057
        $key = "$label ($entity_id)";
1058
        // Strip things like starting/trailing white spaces, line breaks and tags.
1059
        $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key)))));
1060
        // Names containing commas or quotes must be wrapped in quotes.
1061
        if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
1062
          $key = '"' . str_replace('"', '""', $key) . '"';
1063
        }
1064
        $matches[$prefix . $key] = '<div class="reference-autocomplete">' . $label . '</div>';
1065
      }
1066
    }
1067
  }
1068
1069
  drupal_json_output($matches);
1070
}
1071
1072
/**
1073
 * Implements hook_field_formatter_info().
1074
 */
1075
function entityreference_field_formatter_info() {
1076
  return array(
1077
    'entityreference_label' => array(
1078
      'label' => t('Label'),
1079
      'description' => t('Display the label of the referenced entities.'),
1080
      'field types' => array('entityreference'),
1081
      'settings' => array(
1082
        'link' => FALSE,
1083
      ),
1084
    ),
1085
    'entityreference_entity_id' => array(
1086
      'label' => t('Entity id'),
1087
      'description' => t('Display the id of the referenced entities.'),
1088
      'field types' => array('entityreference'),
1089
    ),
1090
    'entityreference_entity_view' => array(
1091
      'label' => t('Rendered entity'),
1092
      'description' => t('Display the referenced entities rendered by entity_view().'),
1093
      'field types' => array('entityreference'),
1094
      'settings' => array(
1095
        'view_mode' => 'default',
1096
        'links' => TRUE,
1097
      ),
1098
    ),
1099
  );
1100
}
1101
1102
/**
1103
 * Implements hook_field_formatter_settings_form().
1104
 */
1105
function entityreference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
1106
  $display = $instance['display'][$view_mode];
1107
  $settings = $display['settings'];
1108
1109
  if ($display['type'] == 'entityreference_label') {
1110
    $element['link'] = array(
1111
      '#title' => t('Link label to the referenced entity'),
1112
      '#type' => 'checkbox',
1113
      '#default_value' => $settings['link'],
1114
    );
1115
  }
1116
1117
  if ($display['type'] == 'entityreference_entity_view') {
1118
    $entity_info = entity_get_info($field['settings']['target_type']);
1119
    $options = array('default' => t('Default'));
1120
    if (!empty($entity_info['view modes'])) {
1121
      foreach ($entity_info['view modes'] as $view_mode => $view_mode_settings) {
1122
        $options[$view_mode] = $view_mode_settings['label'];
1123
      }
1124
    }
1125
1126
    $element['view_mode'] = array(
1127
      '#type' => 'select',
1128
      '#options' => $options,
1129
      '#title' => t('View mode'),
1130
      '#default_value' => $settings['view_mode'],
1131
      '#access' => count($options) > 1,
1132
    );
1133
1134
    $element['links'] = array(
1135
      '#type' => 'checkbox',
1136
      '#title' => t('Show links'),
1137
      '#default_value' => $settings['links'],
1138
    );
1139
  }
1140
1141
  return $element;
1142
}
1143
1144
/**
1145
 * Implements hook_field_formatter_settings_summary().
1146
 */
1147
function entityreference_field_formatter_settings_summary($field, $instance, $view_mode) {
1148
  $display = $instance['display'][$view_mode];
1149
  $settings = $display['settings'];
1150
1151
  $summary = array();
1152
1153
  if ($display['type'] == 'entityreference_label') {
1154
    $summary[] = $settings['link'] ? t('Link to the referenced entity') : t('No link');
1155
  }
1156
1157
  if ($display['type'] == 'entityreference_entity_view') {
1158
    $entity_info = entity_get_info($field['settings']['target_type']);
1159
    $view_mode_label = $settings['view_mode'] == 'default' ? t('Default') : $settings['view_mode'];
1160
    if (isset($entity_info['view modes'][$settings['view_mode']]['label'])) {
1161
      $view_mode_label = $entity_info['view modes'][$settings['view_mode']]['label'];
1162
    }
1163
    $summary[] = t('Rendered as @mode', array('@mode' => $view_mode_label));
1164
    $summary[] = !empty($settings['links']) ? t('Display links') : t('Do not display links');
1165
  }
1166
1167
  return implode('<br />', $summary);
1168
}
1169
1170
/**
1171
 * Implements hook_field_formatter_prepare_view().
1172
 */
1173
function entityreference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
1174
  $target_ids = array();
1175
1176
  // Collect every possible entity attached to any of the entities.
1177
  foreach ($entities as $id => $entity) {
1178
    foreach ($items[$id] as $delta => $item) {
1179
      if (isset($item['target_id'])) {
1180
        $target_ids[] = $item['target_id'];
1181
      }
1182
    }
1183
  }
1184
1185
  if ($target_ids) {
1186
    $target_entities = entity_load($field['settings']['target_type'], $target_ids);
1187
  }
1188
  else {
1189
    $target_entities = array();
1190
  }
1191
1192
  // Iterate through the fieldable entities again to attach the loaded data.
1193
  foreach ($entities as $id => $entity) {
1194
    $rekey = FALSE;
1195
1196
    foreach ($items[$id] as $delta => $item) {
1197
      // Check whether the referenced entity could be loaded.
1198
      if (isset($target_entities[$item['target_id']])) {
1199
        // Replace the instance value with the term data.
1200
        $items[$id][$delta]['entity'] = $target_entities[$item['target_id']];
1201
        // Check whether the user has access to the referenced entity.
1202
        $has_view_access = (entity_access('view', $field['settings']['target_type'], $target_entities[$item['target_id']]) !== FALSE);
1203
        $has_update_access = (entity_access('update', $field['settings']['target_type'], $target_entities[$item['target_id']]) !== FALSE);
1204
        $items[$id][$delta]['access'] = ($has_view_access || $has_update_access);
1205
      }
1206
      // Otherwise, unset the instance value, since the entity does not exist.
1207
      else {
1208
        unset($items[$id][$delta]);
1209
        $rekey = TRUE;
1210
      }
1211
    }
1212
1213
    if ($rekey) {
1214
      // Rekey the items array.
1215
      $items[$id] = array_values($items[$id]);
1216
    }
1217
  }
1218
}
1219
1220
/**
1221
 * Implements hook_field_formatter_view().
1222
 */
1223
function entityreference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
1224
  $result = array();
1225
  $settings = $display['settings'];
1226
1227
  // Rebuild the items list to contain only those with access.
1228
  foreach ($items as $key => $item) {
1229
    if (empty($item['access'])) {
1230
      unset($items[$key]);
1231
    }
1232
  }
1233
1234
  switch ($display['type']) {
1235
    case 'entityreference_label':
1236
      $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity);
1237
1238
      foreach ($items as $delta => $item) {
1239
        $label = $handler->getLabel($item['entity']);
1240
        // If the link is to be displayed and the entity has a uri, display a link.
1241
        // Note the assignment ($url = ) here is intended to be an assignment.
1242
        if ($display['settings']['link'] && ($uri = entity_uri($field['settings']['target_type'], $item['entity']))) {
1243
          $result[$delta] = array('#markup' => l($label, $uri['path'], $uri['options']));
1244
        }
1245
        else {
1246
          $result[$delta] = array('#markup' => check_plain($label));
1247
        }
1248
      }
1249
      break;
1250
1251
    case 'entityreference_entity_id':
1252
      foreach ($items as $delta => $item) {
1253
        $result[$delta] = array('#markup' => check_plain($item['target_id']));
1254
      }
1255
      break;
1256
1257
    case 'entityreference_entity_view':
1258
      foreach ($items as $delta => $item) {
1259
        // Protect ourselves from recursive rendering.
1260
        static $depth = 0;
1261
        $depth++;
1262
        if ($depth > 20) {
1263
          throw new EntityReferenceRecursiveRenderingException(t('Recursive rendering detected when rendering entity @entity_type(@entity_id). Aborting rendering.', array('@entity_type' => $entity_type, '@entity_id' => $item['target_id'])));
1264
        }
1265
1266
        $entity = clone $item['entity'];
1267
        unset($entity->content);
1268
        $result[$delta] = entity_view($field['settings']['target_type'], array($item['target_id'] => $entity), $settings['view_mode'], $langcode, FALSE);
1269
1270
        if (empty($settings['links']) && isset($result[$delta][$field['settings']['target_type']][$item['target_id']]['links'])) {
1271
          $result[$delta][$field['settings']['target_type']][$item['target_id']]['links']['#access'] = FALSE;
1272
        }
1273
        $depth = 0;
1274
      }
1275
      break;
1276
  }
1277
1278
  return $result;
1279
}
1280
1281
/**
1282
 * Exception thrown when the entity view renderer goes into a potentially infinite loop.
1283
 */
1284
class EntityReferenceRecursiveRenderingException extends Exception {}
1285
1286
/**
1287
 * Implements hook_views_api().
1288
 */
1289
function entityreference_views_api() {
1290
  return array(
1291
    'api' => 3,
1292
    'path' => drupal_get_path('module', 'entityreference') . '/views',
1293
  );
1294
}