diff --git a/core/modules/entityreference/README.txt b/core/modules/entityreference/README.txt
new file mode 100644
index 0000000..78706c0
--- /dev/null
+++ b/core/modules/entityreference/README.txt
@@ -0,0 +1,16 @@
+DESCRIPTION
+===========
+Provides a field type that can reference arbitrary entities.
+
+SITE BUILDERS
+=============
+Note that when using a select widget, Entity reference loads all the
+entities in that list in order to get the entity's label. If there are
+too many loaded entities that site might reach its memory limit and crash
+(also known as WSOD). In such a case you are advised to change the widget
+to "autocomplete". If you get a WSOD when trying to edit the field
+settings, you can reach the widget settings directly by navigation to
+
+ admin/structure/types/manage/[ENTITY-TYPE]/fields/[FIELD-NAME]/widget-type
+
+Replace ENTITY-TYPE and FIELD_NAME with the correct values.
diff --git a/core/modules/entityreference/css/entityreference-rtl.admin.css b/core/modules/entityreference/css/entityreference-rtl.admin.css
new file mode 100644
index 0000000..600e3d7
--- /dev/null
+++ b/core/modules/entityreference/css/entityreference-rtl.admin.css
@@ -0,0 +1,4 @@
+
+.entityreference-settings {
+ margin-right: 1.5em;
+}
diff --git a/core/modules/entityreference/css/entityreference.admin.css b/core/modules/entityreference/css/entityreference.admin.css
new file mode 100644
index 0000000..d6066ef
--- /dev/null
+++ b/core/modules/entityreference/css/entityreference.admin.css
@@ -0,0 +1,4 @@
+
+.entityreference-settings {
+ margin-left: 1.5em; /* LTR */
+}
diff --git a/core/modules/entityreference/entityreference.info b/core/modules/entityreference/entityreference.info
new file mode 100644
index 0000000..72a8001
--- /dev/null
+++ b/core/modules/entityreference/entityreference.info
@@ -0,0 +1,4 @@
+name = Entity Reference
+description = Provides a field that can reference other entities.
+core = 8.x
+package = Fields
diff --git a/core/modules/entityreference/entityreference.install b/core/modules/entityreference/entityreference.install
new file mode 100644
index 0000000..f655770
--- /dev/null
+++ b/core/modules/entityreference/entityreference.install
@@ -0,0 +1,41 @@
+ array(
+ 'target_id' => array(
+ 'description' => 'The id of the target entity.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ ),
+ 'indexes' => array(
+ 'target_id' => array('target_id'),
+ ),
+ 'foreign keys' => array(),
+ );
+
+ // Create a foreign key to the target entity type base type.
+ $entity_type = $field['settings']['target_type'];
+ $entity_info = entity_get_info($entity_type);
+
+ $base_table = $entity_info['base table'];
+ $id_column = $entity_info['entity keys']['id'];
+
+ $schema['foreign keys'][$base_table] = array(
+ 'table' => $base_table,
+ 'columns' => array('target_id' => $id_column),
+ );
+
+ return $schema;
+}
diff --git a/core/modules/entityreference/entityreference.module b/core/modules/entityreference/entityreference.module
new file mode 100644
index 0000000..2af9c16
--- /dev/null
+++ b/core/modules/entityreference/entityreference.module
@@ -0,0 +1,871 @@
+ t('Entity Reference'),
+ 'description' => t('This field reference another entity.'),
+ 'settings' => array(
+ // Default to the core target entity type node.
+ 'target_type' => 'node',
+ // The handler for this field.
+ 'handler' => 'base',
+ // The handler settings.
+ 'handler_settings' => array(),
+ ),
+ 'instance_settings' => array(),
+ 'default_widget' => 'options_list',
+ 'default_formatter' => 'entityreference_label',
+ );
+ return $field_info;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function entityreference_menu() {
+ $items = array();
+
+ $items['entityreference/autocomplete/single/%/%/%'] = array(
+ 'title' => 'Entity Reference Autocomplete',
+ 'page callback' => 'entityreference_autocomplete_callback',
+ 'page arguments' => array(2, 3, 4, 5),
+ 'access callback' => 'entityreference_autocomplete_access_callback',
+ 'access arguments' => array(2, 3, 4, 5),
+ 'type' => MENU_CALLBACK,
+ );
+ $items['entityreference/autocomplete/tags/%/%/%'] = array(
+ 'title' => 'Entity Reference Autocomplete',
+ 'page callback' => 'entityreference_autocomplete_callback',
+ 'page arguments' => array(2, 3, 4, 5),
+ 'access callback' => 'entityreference_autocomplete_access_callback',
+ 'access arguments' => array(2, 3, 4, 5),
+ 'type' => MENU_CALLBACK,
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_field_is_empty().
+ */
+function entityreference_field_is_empty($item, $field) {
+ return !isset($item['target_id']) || !is_numeric($item['target_id']);
+}
+
+/**
+ * Returns the PluginManager object for a given entityreference plugin type.
+ *
+ * @param string $plugin_type
+ * The plugin type. One of:
+ * - selection
+ *
+ * @return Drupal\Component\Plugin\PluginManagerInterface
+ * The PluginManager object.
+ */
+function entityreference_get_plugin_manager($plugin_type) {
+ $plugin_types = &drupal_static(__FUNCTION__, array());
+
+ $classes = array(
+ 'selection' => 'Drupal\entityreference\Plugin\Type\Selection\SelectionPluginManager',
+ );
+
+ if (isset($classes[$plugin_type])) {
+ if (!isset($plugin_types[$plugin_type])) {
+ $plugin_types[$plugin_type] = new $classes[$plugin_type]();
+ }
+ return $plugin_types[$plugin_type];
+ }
+}
+
+/**
+ * Get the selection handler for a given entityreference field.
+ */
+function entityreference_get_selection_handler($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
+ $plugin = entityreference_get_plugin_manager('selection')->getDefinition($field['settings']['handler']);
+ $class = $plugin['class'];
+
+ if (class_exists($class)) {
+ return call_user_func(array($class, 'getInstance'), $field, $instance, $entity_type, $entity);
+ }
+
+ return Drupal\entityreference\Plugin\Type\Selection\SelectionBroken::getInstance($field, $instance, $entity_type, $entity);
+}
+
+/**
+ * Implements hook_field_validate().
+ */
+function entityreference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
+ $ids = array();
+ foreach ($items as $delta => $item) {
+ if (!entityreference_field_is_empty($item, $field) && $item['target_id'] !== NULL) {
+ $ids[$item['target_id']] = $delta;
+ }
+ }
+
+ if ($ids) {
+ $valid_ids = entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->validateReferencableEntities(array_keys($ids));
+
+ $invalid_entities = array_diff_key($ids, array_flip($valid_ids));
+ if ($invalid_entities) {
+ foreach ($invalid_entities as $id => $delta) {
+ $errors[$field['field_name']][$langcode][$delta][] = array(
+ 'error' => 'entityreference_invalid_entity',
+ 'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)),
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_field_settings_form().
+ *
+ * The field settings infrastructure is not AJAX enabled by default,
+ * because it doesn't pass over the $form_state.
+ * Build the whole form into a #process in which we actually have access
+ * to the form state.
+ */
+function entityreference_field_settings_form($field, $instance, $has_data) {
+ $form = array(
+ '#type' => 'container',
+ '#attached' => array(
+ 'css' => array(drupal_get_path('module', 'entityreference') . '/css/entityreference.admin.css'),
+ ),
+ '#process' => array(
+ '_entityreference_field_settings_process',
+ '_entityreference_field_settings_ajax_process',
+ ),
+ '#element_validate' => array('_entityreference_field_settings_validate'),
+ '#field' => $field,
+ '#instance' => $instance,
+ '#has_data' => $has_data,
+ );
+ return $form;
+}
+
+function _entityreference_field_settings_process($form, $form_state) {
+ $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field'];
+ $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance'];
+ $has_data = $form['#has_data'];
+
+ $settings = $field['settings'];
+ $settings += array('handler' => 'base');
+
+ // Select the target entity type.
+ $entity_type_options = array();
+ foreach (entity_get_info() as $entity_type => $entity_info) {
+ $entity_type_options[$entity_type] = $entity_info['label'];
+ }
+
+ $form['target_type'] = array(
+ '#type' => 'select',
+ '#title' => t('Target type'),
+ '#options' => $entity_type_options,
+ '#default_value' => $field['settings']['target_type'],
+ '#required' => TRUE,
+ '#description' => t('The entity type that can be referenced through this field.'),
+ '#disabled' => $has_data,
+ '#size' => 1,
+ '#ajax' => TRUE,
+ '#limit_validation_errors' => array(),
+ );
+
+ $handlers = entityreference_get_plugin_manager('selection')->getDefinitions();
+ $handlers_options = array();
+ foreach ($handlers as $handler => $handler_info) {
+ $handlers_options[$handler] = check_plain($handler_info['label']);
+ }
+
+ $form['handler'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Entity selection'),
+ '#tree' => TRUE,
+ '#process' => array('_entityreference_form_process_merge_parent'),
+ );
+
+ $form['handler']['handler'] = array(
+ '#type' => 'select',
+ '#title' => t('Mode'),
+ '#options' => $handlers_options,
+ '#default_value' => $settings['handler'],
+ '#required' => TRUE,
+ '#ajax' => TRUE,
+ '#limit_validation_errors' => array(),
+ );
+ $form['handler_submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Change handler'),
+ '#limit_validation_errors' => array(),
+ '#attributes' => array(
+ 'class' => array('js-hide'),
+ ),
+ '#submit' => array('entityreference_settings_ajax_submit'),
+ );
+
+ $form['handler']['handler_settings'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('entityreference-settings')),
+ );
+
+ $handler = entityreference_get_selection_handler($field, $instance);
+ $form['handler']['handler_settings'] += $handler->settingsForm($field, $instance);
+
+ return $form;
+}
+
+function _entityreference_field_settings_ajax_process($form, $form_state) {
+ _entityreference_field_settings_ajax_process_element($form, $form);
+ return $form;
+}
+
+function _entityreference_field_settings_ajax_process_element(&$element, $main_form) {
+ if (isset($element['#ajax']) && $element['#ajax'] === TRUE) {
+ $element['#ajax'] = array(
+ 'callback' => 'entityreference_settings_ajax',
+ 'wrapper' => $main_form['#id'],
+ 'element' => $main_form['#array_parents'],
+ );
+ }
+
+ foreach (element_children($element) as $key) {
+ _entityreference_field_settings_ajax_process_element($element[$key], $main_form);
+ }
+}
+
+function _entityreference_form_process_merge_parent($element) {
+ $parents = $element['#parents'];
+ array_pop($parents);
+ $element['#parents'] = $parents;
+ return $element;
+}
+
+function _entityreference_element_validate_filter(&$element, &$form_state) {
+ $element['#value'] = array_filter($element['#value']);
+ form_set_value($element, $element['#value'], $form_state);
+}
+
+function _entityreference_field_settings_validate($form, &$form_state) {
+ // Store the new values in the form state.
+ $field = $form['#field'];
+ if (isset($form_state['values']['field'])) {
+ $field['settings'] = $form_state['values']['field']['settings'];
+ }
+ $form_state['entityreference']['field'] = $field;
+
+ unset($form_state['values']['field']['settings']['handler_submit']);
+}
+
+/**
+ * Implements hook_field_instance_settings_form().
+ */
+function entityreference_field_instance_settings_form($field, $instance) {
+ $form['settings'] = array(
+ '#type' => 'container',
+ '#attached' => array(
+ 'css' => array(drupal_get_path('module', 'entityreference') . '/css/entityreference.admin.css'),
+ ),
+ '#weight' => 10,
+ '#tree' => TRUE,
+ '#process' => array(
+ '_entityreference_form_process_merge_parent',
+ '_entityreference_field_instance_settings_form',
+ '_entityreference_field_settings_ajax_process',
+ ),
+ '#element_validate' => array('_entityreference_field_instance_settings_validate'),
+ '#field' => $field,
+ '#instance' => $instance,
+ );
+
+ return $form;
+}
+
+function _entityreference_field_instance_settings_form($form, $form_state) {
+ $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field'];
+ $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance'];
+ return $form;
+}
+
+function _entityreference_field_instance_settings_validate($form, &$form_state) {
+ // Store the new values in the form state.
+ $instance = $form['#instance'];
+ if (isset($form_state['values']['instance'])) {
+ $instance = drupal_array_merge_deep($instance, $form_state['values']['instance']);
+ }
+ $form_state['entityreference']['instance'] = $instance;
+}
+
+
+/**
+ * Ajax callback for the handler settings form.
+ *
+ * @see entityreference_field_settings_form()
+ */
+function entityreference_settings_ajax($form, $form_state) {
+ $trigger = $form_state['triggering_element'];
+ return drupal_array_get_nested_value($form, $trigger['#ajax']['element']);
+}
+
+/**
+ * Submit handler for the non-JS case.
+ *
+ * @see entityreference_field_settings_form()
+ */
+function entityreference_settings_ajax_submit($form, &$form_state) {
+ $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * Implements hook_field_widget_info().
+ */
+function entityreference_field_widget_info() {
+ $widgets['entityreference_autocomplete'] = array(
+ 'label' => t('Autocomplete'),
+ 'description' => t('An autocomplete text field.'),
+ 'field types' => array('entityreference'),
+ 'settings' => array(
+ 'match_operator' => 'CONTAINS',
+ 'size' => 60,
+ // We don't have a default here, because it's not the same between
+ // the two widgets, and the Field API doesn't update default
+ // settings when the widget changes.
+ 'path' => '',
+ ),
+ );
+
+ $widgets['entityreference_autocomplete_tags'] = array(
+ 'label' => t('Autocomplete (Tags style)'),
+ 'description' => t('An autocomplete text field.'),
+ 'field types' => array('entityreference'),
+ 'settings' => array(
+ 'match_operator' => 'CONTAINS',
+ 'size' => 60,
+ // We don't have a default here, because it's not the same between
+ // the two widgets, and the Field API doesn't update default
+ // settings when the widget changes.
+ 'path' => '',
+ ),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+ ),
+ );
+
+ return $widgets;
+}
+
+/**
+ * Implements hook_field_widget_info_alter().
+ *
+ * @todo: Move this to plugin alteration after:
+ * http://drupal.org/node/1751234
+ * http://drupal.org/node/1705702
+ */
+function entityreference_field_widget_info_alter(&$info) {
+ if (module_exists('options')) {
+ $info['options_select']['field types'][] = 'entityreference';
+ $info['options_buttons']['field types'][] = 'entityreference';
+ }
+}
+
+/**
+ * Implements hook_field_widget_settings_form().
+ */
+function entityreference_field_widget_settings_form($field, $instance) {
+ $widget = $instance['widget'];
+ $settings = $widget['settings'] + field_info_widget_settings($widget['type']);
+
+ $form = array();
+
+ if ($widget['type'] == 'entityreference_autocomplete' || $widget['type'] == 'entityreference_autocomplete_tags') {
+ $form['match_operator'] = array(
+ '#type' => 'select',
+ '#title' => t('Autocomplete matching'),
+ '#default_value' => $settings['match_operator'],
+ '#options' => array(
+ 'STARTS_WITH' => t('Starts with'),
+ 'CONTAINS' => t('Contains'),
+ ),
+ '#description' => t('Select the method used to collect autocomplete suggestions. Note that Contains can cause performance issues on sites with thousands of nodes.'),
+ );
+ $form['size'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Size of textfield'),
+ '#default_value' => $settings['size'],
+ '#element_validate' => array('form_validate_number'),
+ // Minimum value for form_validate_number().
+ '#min' => 1,
+ '#required' => TRUE,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Implements hook_options_list().
+ */
+function entityreference_options_list($field, $instance = NULL, $entity_type = NULL, $entity = NULL) {
+ return entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->getReferencableEntities();
+}
+
+/**
+ * Implements hook_query_TAG_alter().
+ */
+function entityreference_query_entityreference_alter(AlterableInterface $query) {
+ $handler = $query->getMetadata('entityreference_selection_handler');
+ $handler->entityFieldQueryAlter($query);
+}
+
+/**
+ * Implements hook_field_widget_form().
+ */
+function entityreference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
+ $entity_type = $instance['entity_type'];
+ $entity = isset($element['#entity']) ? $element['#entity'] : NULL;
+ $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity);
+
+ if ($instance['widget']['type'] == 'entityreference_autocomplete' || $instance['widget']['type'] == 'entityreference_autocomplete_tags') {
+
+ if ($instance['widget']['type'] == 'entityreference_autocomplete') {
+ // We let the Field API handles multiple values for us, only take
+ // care of the one matching our delta.
+ if (isset($items[$delta])) {
+ $items = array($items[$delta]);
+ }
+ else {
+ $items = array();
+ }
+ }
+
+ $entity_ids = array();
+ $entity_labels = array();
+
+ // Build an array of entities ID.
+ foreach ($items as $item) {
+ $entity_ids[] = $item['target_id'];
+ }
+
+ // Load those entities and loop through them to extract their labels.
+ $entities = entity_load_multiple($field['settings']['target_type'], $entity_ids);
+
+ foreach ($entities as $entity_id => $entity_item) {
+ $label = $entity_item->label();
+ $key = "$label ($entity_id)";
+ // Labels containing commas or quotes must be wrapped in quotes.
+ if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
+ $key = '"' . str_replace('"', '""', $key) . '"';
+ }
+ $entity_labels[] = $key;
+ }
+
+ // Prepare the autocomplete path.
+ if (!empty($instance['widget']['settings']['path'])) {
+ $autocomplete_path = $instance['widget']['settings']['path'];
+ }
+ else {
+ $autocomplete_path = $instance['widget']['type'] == 'entityreference_autocomplete' ? 'entityreference/autocomplete/single' : 'entityreference/autocomplete/tags';
+ }
+
+ $autocomplete_path .= '/' . $field['field_name'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/';
+ // Use as a placeholder in the URL when we don't have an entity.
+ // Most webservers collapse two consecutive slashes.
+ $id = 'NULL';
+ if ($entity) {
+ if ($eid = $entity->id()) {
+ $id = $eid;
+ }
+ }
+ $autocomplete_path .= $id;
+
+ if ($instance['widget']['type'] == 'entityreference_autocomplete') {
+ $element += array(
+ '#type' => 'textfield',
+ '#maxlength' => 1024,
+ '#default_value' => implode(', ', $entity_labels),
+ '#autocomplete_path' => $autocomplete_path,
+ '#size' => $instance['widget']['settings']['size'],
+ '#element_validate' => array('_entityreference_autocomplete_validate'),
+ );
+ return array('target_id' => $element);
+ }
+ else {
+ $element += array(
+ '#type' => 'textfield',
+ '#maxlength' => 1024,
+ '#default_value' => implode(', ', $entity_labels),
+ '#autocomplete_path' => $autocomplete_path,
+ '#size' => $instance['widget']['settings']['size'],
+ '#element_validate' => array('_entityreference_autocomplete_tags_validate'),
+ );
+ return $element;
+ }
+ }
+}
+
+function _entityreference_autocomplete_validate($element, &$form_state, $form) {
+ // If a value was entered into the autocomplete...
+ $value = '';
+ if (!empty($element['#value'])) {
+ // Take "label (entity id)', match the id from parenthesis.
+ if (preg_match("/.+\((\d+)\)/", $element['#value'], $matches)) {
+ $value = $matches[1];
+ }
+ else {
+ // Try to get a match from the input string when the user didn't use the
+ // autocomplete but filled in a value manually.
+ $field = field_info_field($element['#field_name']);
+ $handler = entityreference_get_selection_handler($field);
+ $value = $handler->validateAutocompleteInput($element['#value'], $element, $form_state, $form);
+ }
+ }
+ form_set_value($element, $value, $form_state);
+}
+
+function _entityreference_autocomplete_tags_validate($element, &$form_state, $form) {
+ $value = array();
+ // If a value was entered into the autocomplete...
+ if (!empty($element['#value'])) {
+ $entities = drupal_explode_tags($element['#value']);
+ $value = array();
+ foreach ($entities as $entity) {
+ // Take "label (entity id)', match the id from parenthesis.
+ if (preg_match("/.+\((\d+)\)/", $entity, $matches)) {
+ $value[] = array(
+ 'target_id' => $matches[1],
+ );
+ }
+ else {
+ // Try to get a match from the input string when the user didn't use the
+ // autocomplete but filled in a value manually.
+ $field = field_info_field($element['#field_name']);
+ $handler = entityreference_get_selection_handler($field);
+ $value[] = array(
+ 'target_id' => $handler->validateAutocompleteInput($entity, $element, $form_state, $form),
+ );
+ }
+ }
+ }
+ form_set_value($element, $value, $form_state);
+}
+
+/**
+ * Implements hook_field_widget_error().
+ */
+function entityreference_field_widget_error($element, $error) {
+ form_error($element, $error['message']);
+}
+
+/**
+ * Menu Access callback for the autocomplete widget.
+ *
+ * @param $type
+ * The widget type (i.e. 'single' or 'tags').
+ * @param $field_name
+ * The name of the entity-reference field.
+ * @param $entity_type
+ * The entity type.
+ * @param $bundle_name
+ * The bundle name.
+ * @return
+ * True if user can access this menu item.
+ */
+function entityreference_autocomplete_access_callback($type, $field_name, $entity_type, $bundle_name) {
+ if (!$field = field_info_field($field_name)) {
+ return;
+ }
+ if (!$instance = field_info_instance($entity_type, $field_name, $bundle_name)){
+ return;
+ }
+
+ if ($field['type'] != 'entityreference' || !field_access('edit', $field, $entity_type)) {
+ return;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Menu callback: autocomplete the label of an entity.
+ *
+ * @param $type
+ * The widget type (i.e. 'single' or 'tags').
+ * @param $field_name
+ * The name of the entity-reference field.
+ * @param $entity_type
+ * The entity type.
+ * @param $bundle_name
+ * The bundle name.
+ * @param $entity_id
+ * Optional; The entity ID the entity-reference field is attached to.
+ * Defaults to ''.
+ * @param $string
+ * The label of the entity to query by.
+ */
+function entityreference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $entity_id = '', $string = '') {
+ $field = field_info_field($field_name);
+ $instance = field_info_instance($entity_type, $field_name, $bundle_name);
+ $matches = array();
+
+ $target_type = $field['settings']['target_type'];
+
+ $entity = NULL;
+ if ($entity_id !== 'NULL') {
+ $entity = entity_load_single($entity_type, $entity_id);
+ // TODO: Improve when we have entity_access().
+ $entity_access = $target_type == 'node' ? node_access('view', $entity) : TRUE;
+ if (!$entity || !$entity_access) {
+ return MENU_ACCESS_DENIED;
+ }
+ }
+ $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity);
+
+ if ($type == 'tags') {
+ // The user enters a comma-separated list of tags. We only autocomplete the last tag.
+ $tags_typed = drupal_explode_tags($string);
+ $tag_last = drupal_strtolower(array_pop($tags_typed));
+ if (!empty($tag_last)) {
+ $prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : '';
+ }
+ }
+ else {
+ // The user enters a single tag.
+ $prefix = '';
+ $tag_last = $string;
+ }
+
+ if (isset($tag_last)) {
+ // Get an array of matching entities.
+ $entity_labels = $handler->getReferencableEntities($tag_last, $instance['widget']['settings']['match_operator'], 10);
+
+ // Loop through the products and convert them into autocomplete output.
+ foreach ($entity_labels as $entity_id => $label) {
+ $key = "$label ($entity_id)";
+ // Strip things like starting/trailing white spaces, line breaks and tags.
+ $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key)))));
+ // Names containing commas or quotes must be wrapped in quotes.
+ if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) {
+ $key = '"' . str_replace('"', '""', $key) . '"';
+ }
+ $matches[$prefix . $key] = '' . $label . '
';
+ }
+ }
+
+ return new JsonResponse($matches);
+}
+
+/**
+ * Implements hook_field_formatter_info().
+ */
+function entityreference_field_formatter_info() {
+ return array(
+ 'entityreference_label' => array(
+ 'label' => t('Label'),
+ 'description' => t('Display the label of the referenced entities.'),
+ 'field types' => array('entityreference'),
+ 'settings' => array(
+ 'link' => FALSE,
+ ),
+ ),
+ 'entityreference_entity_id' => array(
+ 'label' => t('Entity id'),
+ 'description' => t('Display the id of the referenced entities.'),
+ 'field types' => array('entityreference'),
+ ),
+ 'entityreference_entity_view' => array(
+ 'label' => t('Rendered entity'),
+ 'description' => t('Display the referenced entities rendered by entity_view().'),
+ 'field types' => array('entityreference'),
+ 'settings' => array(
+ 'view_mode' => '',
+ 'links' => TRUE,
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_field_formatter_settings_form().
+ */
+function entityreference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ if ($display['type'] == 'entityreference_label') {
+ $element['link'] = array(
+ '#title' => t('Link label to the referenced entity'),
+ '#type' => 'checkbox',
+ '#default_value' => $settings['link'],
+ );
+ }
+
+ if ($display['type'] == 'entityreference_entity_view') {
+ $entity_info = entity_get_info($field['settings']['target_type']);
+ $options = array();
+ if (!empty($entity_info['view modes'])) {
+ foreach ($entity_info['view modes'] as $view_mode => $view_mode_settings) {
+ $options[$view_mode] = $view_mode_settings['label'];
+ }
+ }
+
+ if (count($options) > 1) {
+ $element['view_mode'] = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => t('View mode'),
+ '#default_value' => $settings['view_mode'],
+ );
+ }
+
+ $element['links'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Show links'),
+ '#default_value' => $settings['links'],
+ );
+ }
+
+ return $element;
+}
+
+/**
+ * Implements hook_field_formatter_settings_summary().
+ */
+function entityreference_field_formatter_settings_summary($field, $instance, $view_mode) {
+ $display = $instance['display'][$view_mode];
+ $settings = $display['settings'];
+
+ $summary = array();
+
+ if ($display['type'] == 'entityreference_label') {
+ $summary[] = $settings['link'] ? t('Link to the referenced entity') : t('No link');
+ }
+
+ if ($display['type'] == 'entityreference_entity_view') {
+ $entity_info = entity_get_info($field['settings']['target_type']);
+ $summary[] = t('Rendered as @mode', array('@mode' => isset($entity_info['view modes'][$settings['view_mode']]['label']) ? $entity_info['view modes'][$settings['view_mode']]['label'] : $settings['view_mode']));
+ $summary[] = !empty($settings['links']) ? t('Display links') : t('Do not display links');
+ }
+
+ return implode('
', $summary);
+}
+
+/**
+ * Implements hook_field_formatter_prepare_view().
+ *
+ * Mark the accessible IDs a user can see. We do not unset unaccessible
+ * values, as other may want to act on those values, even if they can
+ * not be accessed.
+ */
+function entityreference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
+ $target_ids = array();
+
+ // Collect every possible entity attached to any of the entities.
+ foreach ($entities as $id => $entity) {
+ foreach ($items[$id] as $delta => $item) {
+ if (isset($item['target_id'])) {
+ $target_ids[] = $item['target_id'];
+ }
+ }
+ }
+
+ $target_type = $field['settings']['target_type'];
+
+ if ($target_ids) {
+ $target_entities = entity_load_multiple($target_type, $target_ids);
+ }
+ else {
+ $target_entities = array();
+ }
+
+ // Iterate through the fieldable entities again to attach the loaded
+ // data.
+ foreach ($entities as $id => $entity) {
+
+ foreach ($items[$id] as $delta => $item) {
+ $items[$id][$delta]['entity'] = $target_entities[$item['target_id']];
+
+ if (!isset($target_entities[$item['target_id']])) {
+ continue;
+ }
+
+ $entity = $target_entities[$item['target_id']];
+
+ // TODO: Improve when we have entity_access().
+ $entity_access = $target_type == 'node' ? node_access('view', $entity) : TRUE;
+ if (!$entity_access) {
+ continue;
+ }
+
+ // Mark item as accessible.
+ $items[$id][$delta]['access'] = TRUE;
+ }
+ }
+}
+
+/**
+ * Implements hook_field_formatter_view().
+ */
+function entityreference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
+ $result = array();
+ $settings = $display['settings'];
+
+ // Remove unaccessible items.
+ foreach ($items as $delta => $item) {
+ if (empty($item['access'])) {
+ unset($items[$delta]);
+ }
+ }
+
+ switch ($display['type']) {
+ case 'entityreference_label':
+ $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity);
+ foreach ($items as $delta => $item) {
+ $entity = $item['entity'];
+ $label = $entity->label();
+ // If the link is to be displayed and the entity has a uri, display a link.
+ // Note the assignment ($url = ) here is intended to be an assignment.
+ if ($display['settings']['link'] && ($uri = $entity->uri())) {
+ $result[$delta] = array('#markup' => l($label, $uri['path'], $uri['options']));
+ }
+ else {
+ $result[$delta] = array('#markup' => check_plain($label));
+ }
+ }
+ break;
+
+ case 'entityreference_entity_id':
+ foreach ($items as $delta => $item) {
+ $result[$delta] = array('#markup' => check_plain($item['target_id']));
+ }
+ break;
+
+ case 'entityreference_entity_view':
+ foreach ($items as $delta => $item) {
+ // Protect ourselves from recursive rendering.
+ static $depth = 0;
+ $depth++;
+ if ($depth > 20) {
+ 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'])));
+ }
+
+ $entity = clone $item['entity'];
+ unset($entity->content);
+ $result[$delta] = entity_view($field['settings']['target_type'], array($item['target_id'] => $entity), $settings['view_mode'], $langcode, FALSE);
+
+ if (empty($settings['links']) && isset($result[$delta][$field['settings']['target_type']][$item['target_id']]['links'])) {
+ $result[$delta][$field['settings']['target_type']][$item['target_id']]['links']['#access'] = FALSE;
+ }
+ $depth = 0;
+ }
+ break;
+ }
+
+ return $result;
+}
\ No newline at end of file
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/EntityReferenceRecursiveRenderingException.php b/core/modules/entityreference/lib/Drupal/entityreference/EntityReferenceRecursiveRenderingException.php
new file mode 100644
index 0000000..bd4a888
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/EntityReferenceRecursiveRenderingException.php
@@ -0,0 +1,15 @@
+field = $field;
+ $this->instance = $instance;
+ }
+
+ public static function settingsForm($field, $instance) {
+ $form['selection_handler'] = array(
+ '#markup' => t('The selected selection handler is broken.'),
+ );
+ return $form;
+ }
+
+ public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
+ return array();
+ }
+
+ public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') {
+ return 0;
+ }
+
+ public function validateReferencableEntities(array $ids) {
+ return array();
+ }
+
+ public function validateAutocompleteInput($input, &$element, &$form_state, $form) {
+ }
+
+ public function entityFieldQueryAlter(AlterableInterface $query) {
+ }
+
+ public function getLabel($entity) {
+ return '';
+ }
+}
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeComment.php b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeComment.php
new file mode 100644
index 0000000..349abae
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeComment.php
@@ -0,0 +1,61 @@
+getTables();
+ $query->condition(key($tables) . '.status', COMMENT_PUBLISHED);
+ }
+
+ // The Comment module doesn't implement any proper comment access,
+ // and as a consequence doesn't make sure that comments cannot be viewed
+ // when the user doesn't have access to the node.
+ $tables = $query->getTables();
+ $base_table = key($tables);
+ $node_alias = $query->innerJoin('node', 'n', '%alias.nid = ' . $base_table . '.nid');
+ // Pass the query to the node access control.
+ $this->reAlterQuery($query, 'node_access', $node_alias);
+
+ // Alas, the comment entity exposes a bundle, but doesn't have a bundle column
+ // in the database. We have to alter the query ourself to go fetch the
+ // bundle.
+ $conditions = &$query->conditions();
+ foreach ($conditions as $key => &$condition) {
+ if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'node_type') {
+ $condition['field'] = $node_alias . '.type';
+ foreach ($condition['value'] as &$value) {
+ if (substr($value, 0, 13) == 'comment_node_') {
+ $value = substr($value, 13);
+ }
+ }
+ break;
+ }
+ }
+
+ // Passing the query to node_query_node_access_alter() is sadly
+ // insufficient for nodes.
+ // @see EntityReferenceHandler_node::entityFieldQueryAlter()
+ if (!user_access('bypass node access') && !count(module_implements('node_grants'))) {
+ $query->condition($node_alias . '.status', 1);
+ }
+ }
+}
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeFile.php b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeFile.php
new file mode 100644
index 0000000..f300041
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeFile.php
@@ -0,0 +1,37 @@
+getTables();
+ $base_table = key($tables);
+ $query->condition('status', FILE_STATUS_PERMANENT);
+
+ // Access control to files is a very difficult business. For now, we are not
+ // going to give it a shot.
+ // @todo: fix this when core access control is less insane.
+ return $query;
+ }
+
+ public function getLabel($entity) {
+ // The file entity doesn't have a label. More over, the filename is
+ // sometimes empty, so use the basename in that case.
+ return $entity->filename !== '' ? $entity->filename : basename($entity->uri);
+ }
+}
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeNode.php b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeNode.php
new file mode 100644
index 0000000..f33c83c
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeNode.php
@@ -0,0 +1,30 @@
+getTables();
+ $query->condition(key($tables) . '.status', NODE_PUBLISHED);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeTaxonomyTerm.php b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeTaxonomyTerm.php
new file mode 100644
index 0000000..f6c2ae3
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeTaxonomyTerm.php
@@ -0,0 +1,42 @@
+getTables();
+ $base_table = key($tables);
+ $vocabulary_alias = $query->innerJoin('taxonomy_vocabulary', 'n', '%alias.vid = ' . $base_table . '.vid');
+ $query->addMetadata('base_table', $vocabulary_alias);
+ // Pass the query to the taxonomy access control.
+ $this->reAlterQuery($query, 'taxonomy_vocabulary_access', $vocabulary_alias);
+
+ // Also, the taxonomy term entity exposes a bundle, but doesn't have a bundle
+ // column in the database. We have to alter the query ourself to go fetch
+ // the bundle.
+ $conditions = &$query->conditions();
+ foreach ($conditions as $key => &$condition) {
+ if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'vocabulary_machine_name') {
+ $condition['field'] = $vocabulary_alias . '.machine_name';
+ break;
+ }
+ }
+ }
+}
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeUser.php b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeUser.php
new file mode 100644
index 0000000..ce3bfdd
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionEntityTypeUser.php
@@ -0,0 +1,71 @@
+propertyCondition('name', $match, $match_operator);
+ }
+
+ // Adding the 'user_access' tag is sadly insufficient for users: core
+ // requires us to also know about the concept of 'blocked' and
+ // 'active'.
+ if (!user_access('administer users')) {
+ $query->propertyCondition('status', 1);
+ }
+ return $query;
+ }
+
+ public function entityFieldQueryAlter(AlterableInterface $query) {
+ if (user_access('administer users')) {
+ // In addition, if the user is administrator, we need to make sure to
+ // match the anonymous user, that doesn't actually have a name in the
+ // database.
+ $conditions = &$query->conditions();
+ foreach ($conditions as $key => $condition) {
+ if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users.name') {
+ // Remove the condition.
+ unset($conditions[$key]);
+
+ // Re-add the condition and a condition on uid = 0 so that we end up
+ // with a query in the form:
+ // WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0)
+ $or = db_or();
+ $or->condition($condition['field'], $condition['value'], $condition['operator']);
+ // Sadly, the Database layer doesn't allow us to build a condition
+ // in the form ':placeholder = :placeholder2', because the 'field'
+ // part of a condition is always escaped.
+ // As a (cheap) workaround, we separately build a condition with no
+ // field, and concatenate the field and the condition separately.
+ $value_part = db_and();
+ $value_part->condition('anonymous_name', $condition['value'], $condition['operator']);
+ $value_part->compile(Database::getConnection(), $query);
+ $or->condition(db_and()
+ ->where(str_replace('anonymous_name', ':anonymous_name', (string) $value_part), $value_part->arguments() + array(':anonymous_name' => user_format_name(user_load(0))))
+ ->condition('users.uid', 0)
+ );
+ $query->condition($or);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionInterface.php b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionInterface.php
new file mode 100644
index 0000000..23d4c2c
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/Type/Selection/SelectionInterface.php
@@ -0,0 +1,76 @@
+baseDiscovery = new AnnotatedClassDiscovery('entityreference', 'selection');
+ $this->discovery = new CacheDecorator($this->baseDiscovery, $this->cache_key);
+ }
+
+ /**
+ * Overrides Drupal\Component\Plugin\PluginManagerBase::getDefinition().
+ *
+ * @todo Remove when http://drupal.org/node/1778942 is fixed.
+ */
+ public function getDefinition($plugin_id) {
+ $definition = $this->discovery->getDefinition($plugin_id);
+ if (!empty($definition)) {
+ $this->processDefinition($definition, $plugin_id);
+ return $definition;
+ }
+ }
+
+}
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/Plugin/entityreference/selection/SelectionBase.php b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/entityreference/selection/SelectionBase.php
new file mode 100644
index 0000000..76fc16a
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/Plugin/entityreference/selection/SelectionBase.php
@@ -0,0 +1,331 @@
+field = $field;
+ $this->instance = $instance;
+ $this->entity_type = $entity_type;
+ $this->entity = $entity;
+ }
+
+ /**
+ * Implements EntityReferenceHandler::settingsForm().
+ */
+ public static function settingsForm($field, $instance) {
+ $entity_info = entity_get_info($field['settings']['target_type']);
+
+ // Merge-in default values.
+ $field['settings']['handler_settings'] += array(
+ 'target_bundles' => array(),
+ 'sort' => array(
+ 'type' => 'none',
+ )
+ );
+
+ if (!empty($entity_info['entity keys']['bundle'])) {
+ $bundles = array();
+ foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
+ $bundles[$bundle_name] = $bundle_info['label'];
+ }
+
+ $form['target_bundles'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Target bundles'),
+ '#options' => $bundles,
+ '#default_value' => $field['settings']['handler_settings']['target_bundles'],
+ '#size' => 6,
+ '#multiple' => TRUE,
+ '#description' => t('The bundles of the entity type that can be referenced. Optional, leave empty for all bundles.'),
+ '#element_validate' => array('_entityreference_element_validate_filter'),
+ );
+ }
+ else {
+ $form['target_bundles'] = array(
+ '#type' => 'value',
+ '#value' => array(),
+ );
+ }
+
+ $form['sort']['type'] = array(
+ '#type' => 'select',
+ '#title' => t('Sort by'),
+ '#options' => array(
+ 'none' => t("Don't sort"),
+ 'property' => t('A property of the base table of the entity'),
+ 'field' => t('A field attached to this entity'),
+ ),
+ '#ajax' => TRUE,
+ '#limit_validation_errors' => array(),
+ '#default_value' => $field['settings']['handler_settings']['sort']['type'],
+ );
+
+ $form['sort']['settings'] = array(
+ '#type' => 'container',
+ '#attributes' => array('class' => array('entityreference-settings')),
+ '#process' => array('_entityreference_form_process_merge_parent'),
+ );
+
+ if ($field['settings']['handler_settings']['sort']['type'] == 'property') {
+ // Merge-in default values.
+ $field['settings']['handler_settings']['sort'] += array(
+ 'property' => NULL,
+ );
+
+ $form['sort']['settings']['property'] = array(
+ '#type' => 'select',
+ '#title' => t('Sort property'),
+ '#required' => TRUE,
+ '#options' => drupal_map_assoc($entity_info['schema_fields_sql']['base table']),
+ '#default_value' => $field['settings']['handler_settings']['sort']['property'],
+ );
+ }
+ elseif ($field['settings']['handler_settings']['sort']['type'] == 'field') {
+ // Merge-in default values.
+ $field['settings']['handler_settings']['sort'] += array(
+ 'field' => NULL,
+ );
+
+ $fields = array();
+ foreach (field_info_instances($field['settings']['target_type']) as $bundle_name => $bundle_instances) {
+ foreach ($bundle_instances as $instance_name => $instance_info) {
+ $field_info = field_info_field($instance_name);
+ foreach ($field_info['columns'] as $column_name => $column_info) {
+ $fields[$instance_name . ':' . $column_name] = t('@label (column @column)', array('@label' => $instance_info['label'], '@column' => $column_name));
+ }
+ }
+ }
+
+ $form['sort']['settings']['field'] = array(
+ '#type' => 'select',
+ '#title' => t('Sort field'),
+ '#required' => TRUE,
+ '#options' => $fields,
+ '#default_value' => $field['settings']['handler_settings']['sort']['field'],
+ );
+ }
+
+ if ($field['settings']['handler_settings']['sort']['type'] != 'none') {
+ // Merge-in default values.
+ $field['settings']['handler_settings']['sort'] += array(
+ 'direction' => 'ASC',
+ );
+
+ $form['sort']['settings']['direction'] = array(
+ '#type' => 'select',
+ '#title' => t('Sort direction'),
+ '#required' => TRUE,
+ '#options' => array(
+ 'ASC' => t('Ascending'),
+ 'DESC' => t('Descending'),
+ ),
+ '#default_value' => $field['settings']['handler_settings']['sort']['direction'],
+ );
+ }
+
+ return $form;
+ }
+
+ /**
+ * Implements EntityReferenceHandler::getReferencableEntities().
+ */
+ public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
+ $entity_type = $this->field['settings']['target_type'];
+
+ $query = $this->buildEntityFieldQuery($match, $match_operator);
+ if ($limit > 0) {
+ $query->range(0, $limit);
+ }
+
+ $result = $query->execute();
+
+ if (empty($result[$entity_type])) {
+ return array();
+ }
+
+ $options = array();
+ $entities = entity_load_multiple($entity_type, array_keys($result[$entity_type]));
+ foreach ($entities as $entity_id => $entity) {
+ $options[$entity_id] = check_plain($entity->label());
+ }
+
+ return $options;
+ }
+
+ /**
+ * Implements EntityReferenceHandler::countReferencableEntities().
+ */
+ public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') {
+ $query = $this->buildEntityFieldQuery($match, $match_operator);
+ return $query
+ ->count()
+ ->execute();
+ }
+
+ /**
+ * Implements EntityReferenceHandler::validateReferencableEntities().
+ */
+ public function validateReferencableEntities(array $ids) {
+ if ($ids) {
+ $entity_type = $this->field['settings']['target_type'];
+ $query = $this->buildEntityFieldQuery();
+ $query->entityCondition('entity_id', $ids, 'IN');
+ $result = $query->execute();
+ if (!empty($result[$entity_type])) {
+ return array_keys($result[$entity_type]);
+ }
+ }
+
+ return array();
+ }
+
+ /**
+ * Implements EntityReferenceHandler::validateAutocompleteInput().
+ */
+ public function validateAutocompleteInput($input, &$element, &$form_state, $form) {
+ $entities = $this->getReferencableEntities($input, '=', 6);
+ if (empty($entities)) {
+ // Error if there are no entities available for a required field.
+ form_error($element, t('There are no entities matching "%value"', array('%value' => $input)));
+ }
+ elseif (count($entities) > 5) {
+ // Error if there are more than 5 matching entities.
+ form_error($element, t('Many entities are called %value. Specify the one you want by appending the id in parentheses, like "@value (@id)"', array(
+ '%value' => $input,
+ '@value' => $input,
+ '@id' => key($entities),
+ )));
+ }
+ elseif (count($entities) > 1) {
+ // More helpful error if there are only a few matching entities.
+ $multiples = array();
+ foreach ($entities as $id => $name) {
+ $multiples[] = $name . ' (' . $id . ')';
+ }
+ form_error($element, t('Multiple entities match this reference; "%multiple"', array('%multiple' => implode('", "', $multiples))));
+ }
+ else {
+ // Take the one and only matching entity.
+ return key($entities);
+ }
+ }
+
+ /**
+ * Build an EntityFieldQuery to get referencable entities.
+ */
+ protected function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') {
+ $query = new EntityFieldQuery();
+ $query->entityCondition('entity_type', $this->field['settings']['target_type']);
+ if (!empty($this->field['settings']['handler_settings']['target_bundles'])) {
+ $query->entityCondition('bundle', $this->field['settings']['handler_settings']['target_bundles'], 'IN');
+ }
+ if (isset($match)) {
+ $entity_info = entity_get_info($this->field['settings']['target_type']);
+ if (isset($entity_info['entity keys']['label'])) {
+ $query->propertyCondition($entity_info['entity keys']['label'], $match, $match_operator);
+ }
+ }
+
+ // Add a generic entity access tag to the query.
+ $query->addTag($this->field['settings']['target_type'] . '_access');
+ $query->addTag('entityreference');
+ $query->addMetaData('field', $this->field);
+ $query->addMetaData('entityreference_selection_handler', $this);
+
+ // Add the sort option.
+ if (!empty($this->field['settings']['handler_settings']['sort'])) {
+ $sort_settings = $this->field['settings']['handler_settings']['sort'];
+ if ($sort_settings['type'] == 'property') {
+ $query->propertyOrderBy($sort_settings['property'], $sort_settings['direction']);
+ }
+ elseif ($sort_settings['type'] == 'field') {
+ list($field, $column) = explode(':', $sort_settings['field'], 2);
+ $query->fieldOrderBy($field, $column, $sort_settings['direction']);
+ }
+ }
+
+ return $query;
+ }
+
+ /**
+ * Implements EntityReferenceHandler::entityFieldQueryAlter().
+ */
+ public function entityFieldQueryAlter(AlterableInterface $query) {
+ }
+
+ /**
+ * Helper method: pass a query to the alteration system again.
+ *
+ * This allow Entity Reference to add a tag to an existing query, to ask
+ * access control mechanisms to alter it again.
+ */
+ protected function reAlterQuery(AlterableInterface $query, $tag, $base_table) {
+ // Save the old tags and metadata.
+ // For some reason, those are public.
+ $old_tags = $query->alterTags;
+ $old_metadata = $query->alterMetaData;
+
+ $query->alterTags = array($tag => TRUE);
+ $query->alterMetaData['base_table'] = $base_table;
+ drupal_alter(array('query', 'query_' . $tag), $query);
+
+ // Restore the tags and metadata.
+ $query->alterTags = $old_tags;
+ $query->alterMetaData = $old_metadata;
+ }
+}
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/Tests/entityReferenceAdminTest.php b/core/modules/entityreference/lib/Drupal/entityreference/Tests/entityReferenceAdminTest.php
new file mode 100644
index 0000000..661e5b6
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/Tests/entityReferenceAdminTest.php
@@ -0,0 +1,118 @@
+ 'Entity Reference UI',
+ 'description' => 'Tests for the administrative UI.',
+ 'group' => 'Entity Reference',
+ );
+ }
+
+ public static $modules = array('field_ui', 'entityreference');
+
+ public function setUp() {
+ parent::setUp();
+
+ // Create test user.
+ $this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types'));
+ $this->drupalLogin($this->admin_user);
+
+ // Create content type, with underscores.
+ $type_name = strtolower($this->randomName(8)) . '_test';
+ $type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name));
+ $this->type = $type->type;
+ }
+
+ protected function assertFieldSelectOptions($name, $expected_options) {
+ $xpath = $this->buildXPathQuery('//select[@name=:name]', array(':name' => $name));
+ $fields = $this->xpath($xpath);
+ if ($fields) {
+ $field = $fields[0];
+ $options = $this->getAllOptionsList($field);
+ return $this->assertIdentical($options, $expected_options);
+ }
+ else {
+ return $this->fail(t('Unable to find field @name', array('@name' => $name)));
+ }
+ }
+
+ /**
+ * Extract all the options of a select element.
+ */
+ protected function getAllOptionsList($element) {
+ $options = array();
+ // Add all options items.
+ foreach ($element->option as $option) {
+ $options[] = (string) $option['value'];
+ }
+ // TODO: support optgroup.
+ return $options;
+ }
+
+ public function testFieldAdminHandler() {
+ $bundle_path = 'admin/structure/types/manage/' . $this->type;
+
+ // First step: 'Add new field' on the 'Manage fields' page.
+ $this->drupalPost($bundle_path . '/fields', array(
+ 'fields[_add_new_field][label]' => 'Test label',
+ 'fields[_add_new_field][field_name]' => 'test',
+ 'fields[_add_new_field][type]' => 'entityreference',
+ 'fields[_add_new_field][widget_type]' => 'entityreference_autocomplete',
+ ), t('Save'));
+
+ // Node should be selected by default.
+ $this->assertFieldByName('field[settings][target_type]', 'node');
+ // The base handler should be selected by default.
+ $this->assertFieldByName('field[settings][handler]', 'base');
+
+ // The base handler settings should be diplayed.
+ $entity_type = 'node';
+ $entity_info = entity_get_info($entity_type);
+ foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
+ $this->assertFieldByName('field[settings][handler_settings][target_bundles][' . $bundle_name . ']');
+ }
+
+ // Test the sort settings.
+ $options = array('none', 'property', 'field');
+ $this->assertFieldSelectOptions('field[settings][handler_settings][sort][type]', $options);
+ // Option 0: no sort.
+ $this->assertFieldByName('field[settings][handler_settings][sort][type]', 'none');
+ $this->assertNoFieldByName('field[settings][handler_settings][sort][property]');
+ $this->assertNoFieldByName('field[settings][handler_settings][sort][field]');
+ $this->assertNoFieldByName('field[settings][handler_settings][sort][direction]');
+ // Option 1: sort by property.
+ $this->drupalPostAJAX(NULL, array('field[settings][handler_settings][sort][type]' => 'property'), 'field[settings][handler_settings][sort][type]');
+ $this->assertFieldByName('field[settings][handler_settings][sort][property]', '');
+ $this->assertNoFieldByName('field[settings][handler_settings][sort][field]');
+ $this->assertFieldByName('field[settings][handler_settings][sort][direction]', 'ASC');
+ // Option 2: sort by field.
+ $this->drupalPostAJAX(NULL, array('field[settings][handler_settings][sort][type]' => 'field'), 'field[settings][handler_settings][sort][type]');
+ $this->assertNoFieldByName('field[settings][handler_settings][sort][property]');
+ $this->assertFieldByName('field[settings][handler_settings][sort][field]', '');
+ $this->assertFieldByName('field[settings][handler_settings][sort][direction]', 'ASC');
+ // Set back to no sort.
+ $this->drupalPostAJAX(NULL, array('field[settings][handler_settings][sort][type]' => 'none'), 'field[settings][handler_settings][sort][type]');
+
+ // Second step: 'Instance settings' form.
+ $this->drupalPost(NULL, array(), t('Save field settings'));
+
+ // Third step: confirm.
+ $this->drupalPost(NULL, array(), t('Save settings'));
+
+ // Check that the field appears in the overview form.
+ $this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', 'Test label', t('Field was created and appears in the overview page.'));
+ }
+}
diff --git a/core/modules/entityreference/lib/Drupal/entityreference/Tests/entityReferenceSelectionAccessTest.php b/core/modules/entityreference/lib/Drupal/entityreference/Tests/entityReferenceSelectionAccessTest.php
new file mode 100644
index 0000000..b946929
--- /dev/null
+++ b/core/modules/entityreference/lib/Drupal/entityreference/Tests/entityReferenceSelectionAccessTest.php
@@ -0,0 +1,440 @@
+ 'Entity Reference Handlers',
+ 'description' => 'Tests for the base handlers provided by Entity Reference.',
+ 'group' => 'Entity Reference',
+ );
+ }
+
+ public static $modules = array('node', 'comment', 'entityreference');
+
+ protected function assertReferencable($field, $tests, $handler_name) {
+ $handler = entityreference_get_selection_handler($field);
+
+ foreach ($tests as $test) {
+ foreach ($test['arguments'] as $arguments) {
+ $result = call_user_func_array(array($handler, 'getReferencableEntities'), $arguments);
+ $this->assertEqual($result, $test['result'], t('Valid result set returned by @handler.', array('@handler' => $handler_name)));
+
+ $result = call_user_func_array(array($handler, 'countReferencableEntities'), $arguments);
+ $this->assertEqual($result, count($test['result']), t('Valid count returned by @handler.', array('@handler' => $handler_name)));
+ }
+ }
+ }
+
+ /**
+ * Test the node-specific overrides of the entity handler.
+ */
+ public function testNodeHandler() {
+ // Build a fake field instance.
+ $field = array(
+ 'translatable' => FALSE,
+ 'entity_types' => array(),
+ 'settings' => array(
+ 'handler' => 'base',
+ 'target_type' => 'node',
+ 'handler_settings' => array(
+ 'target_bundles' => array(),
+ ),
+ ),
+ 'field_name' => 'test_field',
+ 'type' => 'entityreference',
+ 'cardinality' => '1',
+ );
+
+ // Build a set of test data.
+ // Titles contain HTML-special characters to test escaping.
+ $node_values = array(
+ 'published1' => array(
+ 'type' => 'article',
+ 'status' => NODE_PUBLISHED,
+ 'title' => 'Node published1 (<&>)',
+ 'uid' => 1,
+ ),
+ 'published2' => array(
+ 'type' => 'article',
+ 'status' => NODE_PUBLISHED,
+ 'title' => 'Node published2 (<&>)',
+ 'uid' => 1,
+ ),
+ 'unpublished' => array(
+ 'type' => 'article',
+ 'status' => NODE_NOT_PUBLISHED,
+ 'title' => 'Node unpublished (<&>)',
+ 'uid' => 1,
+ ),
+ );
+
+ $nodes = array();
+ $node_labels = array();
+ foreach ($node_values as $key => $values) {
+ $node = entity_create('node', $values);
+ $node->save();
+ $nodes[$key] = $node;
+ $node_labels[$key] = check_plain($node->label());
+ }
+
+ // Test as a non-admin.
+ $normal_user = $this->drupalCreateUser(array('access content'));
+ $GLOBALS['user'] = $normal_user;
+ $referencable_tests = array(
+ array(
+ 'arguments' => array(
+ array(NULL, 'CONTAINS'),
+ ),
+ 'result' => array(
+ $nodes['published1']->nid => $node_labels['published1'],
+ $nodes['published2']->nid => $node_labels['published2'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('published1', 'CONTAINS'),
+ array('Published1', 'CONTAINS'),
+ ),
+ 'result' => array(
+ $nodes['published1']->nid => $node_labels['published1'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('published2', 'CONTAINS'),
+ array('Published2', 'CONTAINS'),
+ ),
+ 'result' => array(
+ $nodes['published2']->nid => $node_labels['published2'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('invalid node', 'CONTAINS'),
+ ),
+ 'result' => array(),
+ ),
+ array(
+ 'arguments' => array(
+ array('Node unpublished', 'CONTAINS'),
+ ),
+ 'result' => array(),
+ ),
+ );
+ $this->assertReferencable($field, $referencable_tests, 'Node handler');
+
+ // Test as an admin.
+ $admin_user = $this->drupalCreateUser(array('access content', 'bypass node access'));
+ $GLOBALS['user'] = $admin_user;
+ $referencable_tests = array(
+ array(
+ 'arguments' => array(
+ array(NULL, 'CONTAINS'),
+ ),
+ 'result' => array(
+ $nodes['published1']->nid => $node_labels['published1'],
+ $nodes['published2']->nid => $node_labels['published2'],
+ $nodes['unpublished']->nid => $node_labels['unpublished'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('Node unpublished', 'CONTAINS'),
+ ),
+ 'result' => array(
+ $nodes['unpublished']->nid => $node_labels['unpublished'],
+ ),
+ ),
+ );
+ $this->assertReferencable($field, $referencable_tests, 'Node handler (admin)');
+ }
+
+ /**
+ * Test the user-specific overrides of the entity handler.
+ */
+ public function testUserHandler() {
+ // Build a fake field instance.
+ $field = array(
+ 'translatable' => FALSE,
+ 'entity_types' => array(),
+ 'settings' => array(
+ 'handler' => 'base',
+ 'target_type' => 'user',
+ 'handler_settings' => array(
+ 'target_bundles' => array(),
+ ),
+ ),
+ 'field_name' => 'test_field',
+ 'type' => 'entityreference',
+ 'cardinality' => '1',
+ );
+
+ // Build a set of test data.
+ $user_values = array(
+ 'anonymous' => user_load(0),
+ 'admin' => user_load(1),
+ 'non_admin' => array(
+ 'name' => 'non_admin <&>',
+ 'mail' => 'non_admin@example.com',
+ 'roles' => array(),
+ 'pass' => user_password(),
+ 'status' => 1,
+ ),
+ 'blocked' => array(
+ 'name' => 'blocked <&>',
+ 'mail' => 'blocked@example.com',
+ 'roles' => array(),
+ 'pass' => user_password(),
+ 'status' => 0,
+ ),
+ );
+
+ $user_values['anonymous']->name = config('user.settings')->get('anonymous');
+ $users = array();
+
+ $user_labels = array();
+ foreach ($user_values as $key => $values) {
+ if (is_array($values)) {
+ $account = entity_create('user', $values);
+ $account->save();
+ }
+ else {
+ $account = $values;
+ }
+ $users[$key] = $account;
+ $user_labels[$key] = check_plain($account->name);
+ }
+
+ // Test as a non-admin.
+ $GLOBALS['user'] = $users['non_admin'];
+ $referencable_tests = array(
+ array(
+ 'arguments' => array(
+ array(NULL, 'CONTAINS'),
+ ),
+ 'result' => array(
+ $users['admin']->uid => $user_labels['admin'],
+ $users['non_admin']->uid => $user_labels['non_admin'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('non_admin', 'CONTAINS'),
+ array('NON_ADMIN', 'CONTAINS'),
+ ),
+ 'result' => array(
+ $users['non_admin']->uid => $user_labels['non_admin'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('invalid user', 'CONTAINS'),
+ ),
+ 'result' => array(),
+ ),
+ array(
+ 'arguments' => array(
+ array('blocked', 'CONTAINS'),
+ ),
+ 'result' => array(),
+ ),
+ );
+ $this->assertReferencable($field, $referencable_tests, 'User handler');
+
+ $GLOBALS['user'] = $users['admin'];
+ $referencable_tests = array(
+ array(
+ 'arguments' => array(
+ array(NULL, 'CONTAINS'),
+ ),
+ 'result' => array(
+ $users['anonymous']->uid => $user_labels['anonymous'],
+ $users['admin']->uid => $user_labels['admin'],
+ $users['non_admin']->uid => $user_labels['non_admin'],
+ $users['blocked']->uid => $user_labels['blocked'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('blocked', 'CONTAINS'),
+ ),
+ 'result' => array(
+ $users['blocked']->uid => $user_labels['blocked'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('Anonymous', 'CONTAINS'),
+ array('anonymous', 'CONTAINS'),
+ ),
+ 'result' => array(
+ $users['anonymous']->uid => $user_labels['anonymous'],
+ ),
+ ),
+ );
+ $this->assertReferencable($field, $referencable_tests, 'User handler (admin)');
+ }
+
+ /**
+ * Test the comment-specific overrides of the entity handler.
+ */
+ public function testCommentHandler() {
+ // Build a fake field instance.
+ $field = array(
+ 'translatable' => FALSE,
+ 'entity_types' => array(),
+ 'settings' => array(
+ 'handler' => 'base',
+ 'target_type' => 'comment',
+ 'handler_settings' => array(
+ 'target_bundles' => array(),
+ ),
+ ),
+ 'field_name' => 'test_field',
+ 'type' => 'entityreference',
+ 'cardinality' => '1',
+ );
+
+ // Build a set of test data.
+ $node_values = array(
+ 'published' => array(
+ 'type' => 'article',
+ 'status' => 1,
+ 'title' => 'Node published',
+ 'uid' => 1,
+ ),
+ 'unpublished' => array(
+ 'type' => 'article',
+ 'status' => 0,
+ 'title' => 'Node unpublished',
+ 'uid' => 1,
+ ),
+ );
+ $nodes = array();
+ foreach ($node_values as $key => $values) {
+ $node = entity_create('node', $values);
+ $node->save();
+ $nodes[$key] = $node;
+ }
+
+ $comment_values = array(
+ 'published_published' => array(
+ 'nid' => $nodes['published']->nid,
+ 'uid' => 1,
+ 'cid' => NULL,
+ 'pid' => 0,
+ 'status' => COMMENT_PUBLISHED,
+ 'subject' => 'Comment Published <&>',
+ 'language' => LANGUAGE_NOT_SPECIFIED,
+ ),
+ 'published_unpublished' => array(
+ 'nid' => $nodes['published']->nid,
+ 'uid' => 1,
+ 'cid' => NULL,
+ 'pid' => 0,
+ 'status' => COMMENT_NOT_PUBLISHED,
+ 'subject' => 'Comment Unpublished <&>',
+ 'language' => LANGUAGE_NOT_SPECIFIED,
+ ),
+ 'unpublished_published' => array(
+ 'nid' => $nodes['unpublished']->nid,
+ 'uid' => 1,
+ 'cid' => NULL,
+ 'pid' => 0,
+ 'status' => COMMENT_NOT_PUBLISHED,
+ 'subject' => 'Comment Published on Unpublished node <&>',
+ 'language' => LANGUAGE_NOT_SPECIFIED,
+ ),
+ );
+
+ $comments = array();
+ $comment_labels = array();
+ foreach ($comment_values as $key => $values) {
+ $comment = entity_create('comment', $values);
+ $comment->save();
+ $comments[$key] = $comment;
+ $comment_labels[$key] = check_plain($comment->label());
+ }
+
+ // Test as a non-admin.
+ $normal_user = $this->drupalCreateUser(array('access content', 'access comments'));
+ $GLOBALS['user'] = $normal_user;
+ $referencable_tests = array(
+ array(
+ 'arguments' => array(
+ array(NULL, 'CONTAINS'),
+ ),
+ 'result' => array(
+ $comments['published_published']->cid => $comment_labels['published_published'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('Published', 'CONTAINS'),
+ ),
+ 'result' => array(
+ $comments['published_published']->cid => $comment_labels['published_published'],
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ array('invalid comment', 'CONTAINS'),
+ ),
+ 'result' => array(),
+ ),
+ array(
+ 'arguments' => array(
+ array('Comment Unpublished', 'CONTAINS'),
+ ),
+ 'result' => array(),
+ ),
+ );
+ $this->assertReferencable($field, $referencable_tests, 'Comment handler');
+
+ // Test as a comment admin.
+ $admin_user = $this->drupalCreateUser(array('access content', 'access comments', 'administer comments'));
+ $GLOBALS['user'] = $admin_user;
+ $referencable_tests = array(
+ array(
+ 'arguments' => array(
+ array(NULL, 'CONTAINS'),
+ ),
+ 'result' => array(
+ $comments['published_published']->cid => $comment_labels['published_published'],
+ $comments['published_unpublished']->cid => $comment_labels['published_unpublished'],
+ ),
+ ),
+ );
+ $this->assertReferencable($field, $referencable_tests, 'Comment handler (comment admin)');
+
+ // Test as a node and comment admin.
+ $admin_user = $this->drupalCreateUser(array('access content', 'access comments', 'administer comments', 'bypass node access'));
+ $GLOBALS['user'] = $admin_user;
+ $referencable_tests = array(
+ array(
+ 'arguments' => array(
+ array(NULL, 'CONTAINS'),
+ ),
+ 'result' => array(
+ $comments['published_published']->cid => $comment_labels['published_published'],
+ $comments['published_unpublished']->cid => $comment_labels['published_unpublished'],
+ $comments['unpublished_published']->cid => $comment_labels['unpublished_published'],
+ ),
+ ),
+ );
+ $this->assertReferencable($field, $referencable_tests, 'Comment handler (comment + node admin)');
+ }
+}