diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module index fcf3ef5..7205e95 100644 --- a/core/modules/entity_reference/entity_reference.module +++ b/core/modules/entity_reference/entity_reference.module @@ -109,7 +109,7 @@ function entity_reference_field_is_empty($item, $field) { * * Create an entity on the fly. */ -function entity_reference_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { +function entity_reference_field_presave(EntityInterface $entity, $field, $instance, $langcode, &$items) { global $user; $target_type = $field['settings']['target_type']; $entity_info = entity_get_info($target_type); @@ -143,7 +143,7 @@ function entity_reference_field_presave($entity_type, $entity, $field, $instance /** * Implements hook_field_validate(). */ -function entity_reference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { +function entity_reference_field_validate(EntityInterface $entity = NULL, $field, $instance, $langcode, $items, &$errors) { $ids = array(); foreach ($items as $delta => $item) { if (!entity_reference_field_is_empty($item, $field) && $item['target_id'] !== 'auto_create') { @@ -449,15 +449,18 @@ function entity_reference_autocomplete_access_callback($type, $field_name, $enti * @param string $entity_id * (optional) The entity ID the entity-reference field is attached to. * Defaults to ''. - * @param string $string - * The label of the entity to query by. * * @return \Symfony\Component\HttpFoundation\JsonResponse */ -function entity_reference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $entity_id = '', $string = '') { +function entity_reference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $entity_id = '') { $field = field_info_field($field_name); $instance = field_info_instance($entity_type, $field_name, $bundle_name); + // Get the typed string, if exists from the URL. + $tags_typed = drupal_container()->get('request')->query->get('q'); + $tags_typed = drupal_explode_tags($tags_typed); + $string = drupal_strtolower(array_pop($tags_typed)); + return entity_reference_autocomplete_callback_get_matches($type, $field, $instance, $entity_type, $entity_id, $string); } @@ -517,7 +520,8 @@ function entity_reference_autocomplete_callback_get_matches($type, $field, $inst if (isset($tag_last)) { // Get an array of matching entities. - $entity_labels = $handler->getReferencableEntities($tag_last, $instance['widget']['settings']['match_operator'], 10); + $match_operator = !empty($instance['widget']['settings']['match_operator']) ? $instance['widget']['settings']['match_operator'] : 'CONTAINS'; + $entity_labels = $handler->getReferencableEntities($tag_last, $match_operator, 10); // Loop through the products and convert them into autocomplete output. foreach ($entity_labels as $values) { diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceEntityFormatter.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceEntityFormatter.php index e32621c..1c4a0e8 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceEntityFormatter.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceEntityFormatter.php @@ -97,13 +97,19 @@ public function viewElements(EntityInterface $entity, $langcode, array $items) { throw new RecursiveRenderingException(format_string('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); - $elements[$delta] = entity_view($entity, $view_mode, $langcode); - - if (empty($links) && isset($result[$delta][$target_type][$item['target_id']]['links'])) { - // Hide the element links. - $elements[$delta][$target_type][$item['target_id']]['links']['#access'] = FALSE; + if (!empty($item['entity'])) { + $entity = clone $item['entity']; + unset($entity->content); + $elements[$delta] = entity_view($entity, $view_mode, $langcode); + + if (empty($links) && isset($result[$delta][$target_type][$item['target_id']]['links'])) { + // Hide the element links. + $elements[$delta][$target_type][$item['target_id']]['links']['#access'] = FALSE; + } + } + else { + // This is an "auto_create" item. + $elements[$delta] = array('#markup' => $item['label']); } $depth = 0; } diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceFormatterBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceFormatterBase.php index f6b5fac..df356bf 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceFormatterBase.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceFormatterBase.php @@ -65,21 +65,29 @@ public function prepareView(array $entities, $langcode, array &$items) { foreach ($items[$id] as $delta => $item) { // If we have a revision-ID, the key uses it as-well. $identifier = !empty($item['revision_id']) ? $item['target_id'] . ':' . $item['revision_id'] : $item['target_id']; - if (!isset($target_entities[$identifier])) { - // The entity no longer exists, so remove the key. - $rekey = TRUE; - unset($items[$id][$delta]); - continue; + if ($item['target_id'] != 'auto_create') { + if (!isset($target_entities[$identifier])) { + // The entity no longer exists, so remove the key. + $rekey = TRUE; + unset($items[$id][$delta]); + continue; + } + + $entity = $target_entities[$identifier]; + $items[$id][$delta]['entity'] = $entity; + + // @todo: Improve when we have entity_access(). + $entity_access = $target_type == 'node' ? node_access('view', $entity) : TRUE; + if (!$entity_access) { + continue; + } } - - $items[$id][$delta]['entity'] = $target_entities[$identifier]; - - $entity = $target_entities[$identifier]; - - // @todo: Improve when we have entity_access(). - $entity_access = $target_type == 'node' ? node_access('view', $entity) : TRUE; - if (!$entity_access) { - continue; + else { + // This is an "auto_create" item, so allow access to it, as the entity doesn't + // exists yet, and we are probably in a preview. + $items[$id][$delta]['entity'] = FALSE; + // Add the label as a special key, as we cannot use entity_label(). + $items[$id][$delta]['label'] = $item['label']; } // Mark item as accessible. @@ -96,7 +104,7 @@ public function prepareView(array $entities, $langcode, array &$items) { /** * Overrides Drupal\field\Plugin\Type\Formatter\FormatterBase::viewElements(). * - * @see Drupal\entity_reference\Plugin\field\formatter\EntityReferenceFormatterBase::prepareView(). + * @see Drupal\entity_reference\Plugin\field\formatter\EntityReferenceFormatterBase::viewElements(). */ public function viewElements(EntityInterface $entity, $langcode, array $items) { // Remove un-accessible items. diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceIdFormatter.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceIdFormatter.php index 148e64d..d34c706 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceIdFormatter.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceIdFormatter.php @@ -34,7 +34,9 @@ public function viewElements(EntityInterface $entity, $langcode, array $items) { $elements = array(); foreach ($items as $delta => $item) { - $elements[$delta] = array('#markup' => check_plain($item['target_id'])); + if (!empty($item['entity'])) { + $elements[$delta] = array('#markup' => check_plain($item['target_id'])); + } } return $elements; diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceLabelFormatter.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceLabelFormatter.php index efae069..25e9509 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceLabelFormatter.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/formatter/EntityReferenceLabelFormatter.php @@ -63,15 +63,25 @@ public function viewElements(EntityInterface $entity, $langcode, array $items) { $elements = array(); 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. - if ($this->getSetting('link') && $uri = $entity->uri()) { - $elements[$delta] = array('#markup' => l($label, $uri['path'], $uri['options'])); + if ($entity = $item['entity']) { + $label = $entity->label(); + // If the link is to be displayed and the entity has a uri, + // display a link. + if ($this->getSetting('link') && $uri = $entity->uri()) { + $elements[$delta] = array( + '#type' => 'link', + '#title' => $label, + '#href' => $uri['path'], + '#options' => $uri['options'], + ); + } + else { + $elements[$delta] = array('#markup' => check_plain($label)); + } } else { - $elements[$delta] = array('#markup' => check_plain($label)); + // This is an "auto_create" item. + $elements[$delta] = array('#markup' => $item['label']); } } diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteTagsWidget.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteTagsWidget.php index 053f16a..2cc2b84 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteTagsWidget.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteTagsWidget.php @@ -30,7 +30,8 @@ * settings = { * "match_operator" = "CONTAINS", * "size" = 60, - * "path" = "" + * "path" = "", + * "placeholder" = "" * }, * multiple_values = FIELD_BEHAVIOR_CUSTOM * ) diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidget.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidget.php index c0343bd..fb33558 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidget.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidget.php @@ -30,7 +30,8 @@ * settings = { * "match_operator" = "CONTAINS", * "size" = 60, - * "path" = "" + * "path" = "", + * "placeholder" = "" * } * ) */ @@ -82,7 +83,7 @@ public function elementValidate($element, &$form_state, $form) { '_weight' => $element['#weight'], ); // Change the element['#parents'], so in form_set_value() we - // popualte the correct key. + // populate the correct key. array_pop($element['#parents']); } } diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php index f00a8c2..46bd596 100644 --- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/field/widget/AutocompleteWidgetBase.php @@ -40,6 +40,14 @@ public function settingsForm(array $form, array &$form_state) { '#required' => TRUE, ); + $element['placeholder'] = array( + '#type' => 'textfield', + '#title' => t('Placeholder'), + '#default_value' => $this->getSetting('placeholder'), + '#description' => t('The placeholder is a short hint (a word or short phrase) intended to aid the user with data entry. A hint could be a sample value or a brief description of the expected format.'), + ); + + return $element; } @@ -80,6 +88,7 @@ protected function prepareElement(array $items, $delta, array $element, $langcod '#default_value' => implode(', ', $this->getLabels($items)), '#autocomplete_path' => $autocomplete_path, '#size' => $this->getSetting('size'), + '#placeholder' => $this->getSetting('placeholder'), '#element_validate' => array(array($this, 'elementValidate')), ); return $element; diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceAutocompleteTest.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceAutocompleteTest.php new file mode 100644 index 0000000..bf19d0e --- /dev/null +++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Tests/EntityReferenceAutocompleteTest.php @@ -0,0 +1,114 @@ + 'Autocomplete', + 'description' => 'Tests autocomplete menu item.', + 'group' => 'Entity Reference', + ); + } + + function setUp() { + parent::setUp(); + + $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access')); + $this->drupalLogin($this->admin_user); + $this->vocabulary = $this->createVocabulary(); + + $this->field_name = 'taxonomy_' . $this->vocabulary->id(); + + $field = array( + 'field_name' => $this->field_name, + 'type' => 'entity_reference', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'target_type' => 'taxonomy_term', + ), + ); + field_create_field($field); + + $this->instance = array( + 'field_name' => $this->field_name, + 'bundle' => 'article', + 'entity_type' => 'node', + 'widget' => array( + 'type' => 'options_select', + ), + 'settings' => array( + 'handler' => 'base', + 'handler_settings' => array( + 'target_bundles' => array( + $this->vocabulary->id(), + ), + 'auto_create' => TRUE, + ), + ), + ); + field_create_instance($this->instance); + entity_get_display('node', 'article', 'default') + ->setComponent($this->instance['field_name'], array( + 'type' => 'entity_reference_label', + )) + ->save(); + } + + /** + * Tests autocompletion edge cases with slashes in the names. + */ + function testTermAutocompletion() { + // Add a term with a slash in the name. + $first_term = $this->createTerm($this->vocabulary); + $first_term->name = '10/16/2011'; + taxonomy_term_save($first_term); + // Add another term that differs after the slash character. + $second_term = $this->createTerm($this->vocabulary); + $second_term->name = '10/17/2011'; + taxonomy_term_save($second_term); + // Add another term that has both a comma and a slash character. + $third_term = $this->createTerm($this->vocabulary); + $third_term->name = 'term with, a comma and / a slash'; + taxonomy_term_save($third_term); + + // Set the path prefix to point to entity reference's autocomplete path. + $path_prefix = 'entity_reference/autocomplete/single/' . $this->field_name . '/node/article/NULL'; + + // Try to autocomplete a term name that matches both terms. + // We should get both term in a json encoded string. + $input = '10/'; + $result = $this->drupalGet($path_prefix, array('query' => array('q' => $input))); + $data = drupal_json_decode($result); + $this->assertEqual(strip_tags($data[$first_term->name. ' (1)']), check_plain($first_term->name), 'Autocomplete returned the first matching term'); + $this->assertEqual(strip_tags($data[$second_term->name. ' (2)']), check_plain($second_term->name), 'Autocomplete returned the second matching term'); + + // Try to autocomplete a term name that matches first term. + // We should only get the first term in a json encoded string. + $input = '10/16'; + $this->drupalGet($path_prefix, array('query' => array('q' => $input))); + $target = array($first_term->name . ' (1)' => '
' . check_plain($first_term->name) . '
'); + $this->assertRaw(drupal_json_encode($target), 'Autocomplete returns only the expected matching term.'); + + // Try to autocomplete a term name with both a comma and a slash. + $input = '"term with, comma and / a'; + $this->drupalGet($path_prefix, array('query' => array('q' => $input))); + $n = $third_term->name; + // Term names containing commas or quotes must be wrapped in quotes. + if (strpos($third_term->name, ',') !== FALSE || strpos($third_term->name, '"') !== FALSE) { + $n = '"' . str_replace('"', '""', $third_term->name) . ' (3)"'; + } + $target = array($n => '
' . check_plain($third_term->name) . '
'); + $this->assertRaw(drupal_json_encode($target), 'Autocomplete returns a term containing a comma and a slash.'); + } +} diff --git a/core/modules/field/field.api.php b/core/modules/field/field.api.php index 27304f4..3386eac 100644 --- a/core/modules/field/field.api.php +++ b/core/modules/field/field.api.php @@ -1076,7 +1076,7 @@ function hook_field_attach_view_alter(&$output, $context) { // Append RDF term mappings on displayed taxonomy links. foreach (element_children($output) as $field_name) { $element = &$output[$field_name]; - if ($element['#field_type'] == 'taxonomy_term_reference' && $element['#formatter'] == 'taxonomy_term_reference_link') { + if ($element['#field_type'] == 'entity_reference' && $element['#formatter'] == 'entity_reference_label') { foreach ($element['#items'] as $delta => $item) { $term = $item['taxonomy_term']; if (!empty($term->rdf_mapping['rdftype'])) { diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php index bf02e2b..c7c9984 100644 --- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php +++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php @@ -95,7 +95,7 @@ function addField($field, $type, $langcode) { // field), a field API field (a configurable field). $specifier = $specifiers[$key]; // First, check for field API fields by trying to retrieve the field specified. - // Normally it is a field name, but field_purge_batch() is passing in + // Normally it is a field name, but field_purge_batch() is passing in // id:$field_id so check that first. if (substr($specifier, 0, 3) == 'id:') { $field = field_info_field_by_id(substr($specifier, 3)); @@ -125,9 +125,11 @@ function addField($field, $type, $langcode) { // also use the property definitions for column. if ($key < $count) { $relationship_specifier = $specifiers[$key + 1]; - $propertyDefinitions = typed_data() - ->create(array('type' => $field['type'] . '_field')) - ->getPropertyDefinitions(); + + // Get the field definitions form a mocked entity. + $entity = entity_create($entity_type, array()); + $propertyDefinitions = $entity->{$field['field_name']}->getPropertyDefinitions(); + // If the column is not yet known, ie. the // $node->field_image->entity case then use the id source as the // column. diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php index cf27445..bc80741 100644 --- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php +++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php @@ -54,7 +54,8 @@ protected function mapFromStorageRecords(array $records, $load_revision = FALSE) $values = isset($property_values[$id]) ? $property_values[$id] : array(); foreach ($record as $name => $value) { - $values[$name][LANGUAGE_DEFAULT][0]['value'] = $value; + $key = $name == 'user_id' ? 'target_id' : 'value'; + $values[$name][LANGUAGE_DEFAULT][0][$key] = $value; } $entity = new $this->entityClass($values, $this->entityType); $records[$id] = $entity; diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/field/formatter/EntityReferenceTaxonomyTermRssFormatter.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/field/formatter/EntityReferenceTaxonomyTermRssFormatter.php new file mode 100644 index 0000000..c528985 --- /dev/null +++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/field/formatter/EntityReferenceTaxonomyTermRssFormatter.php @@ -0,0 +1,50 @@ + $item) { + $entity->rss_elements[] = array( + 'key' => 'category', + 'value' => $item['target_id'] != 'autocreate' ? $item['entity']->label() : $item['label'], + 'attributes' => array( + 'domain' => $item['target_id'] != 'autocreate' ? url('taxonomy/term/' . $item['target_id'], array('absolute' => TRUE)) : '', + ), + ); + } + + return $elements; + } +}