diff --git a/entityreference_autocomplete.info b/entityreference_autocomplete.info index d778524..d3bddab 100644 --- a/entityreference_autocomplete.info +++ b/entityreference_autocomplete.info @@ -1,5 +1,9 @@ name = Entity Reference Autocomplete description = A Form API element type to reference arbitrary entities through an autocomplete textfield core = 7.x +php = 5.3 -dependencies[] = entity +autoload[tests/src][] = Drupal\Tests\entity_reference_autocomplete + +; Dependencies for running autotests. +test_dependencies[] = autoload (>=7.x-1.3) diff --git a/entityreference_autocomplete.module b/entityreference_autocomplete.module index 645cebd..57d94f3 100644 --- a/entityreference_autocomplete.module +++ b/entityreference_autocomplete.module @@ -92,22 +92,94 @@ function entityreference_autocomplete_element_info() { * * @return string|bool * The value to be placed in $element['#value']. + * + * @example + * The "#default_value" can be any one of: + * + * @code + * // A single entity ID. + * $element['#default_value'] = 1234; + * + * // An array of entity IDs. + * $element['#default_value'] = array(1234, 1235); + * + * // A single entity. + * $element['#default_value'] = node_load(1234); + * + * // An array of entities. + * $element['#default_value'] = array(node_load(1234), node_load(1235)); + * + * // A single entity structure. + * $element['#default_value'] = array( + * 'entity_id' => 1234, + * 'entity_type' => 'node', + * 'entity_label' => 'Label', + * 'entity_bundle' => 'article', + * ); + * + * // An array of entity structures. + * $element['#default_value'] = array( + * array( + * 'entity_id' => 1234, + * 'entity_type' => 'node', + * 'entity_label' => 'Label 1', + * 'entity_bundle' => 'article', + * ), + * array( + * 'entity_id' => 1235, + * 'entity_type' => 'node', + * 'entity_label' => 'Label 2', + * 'entity_bundle' => 'article', + * ), + * ); + * @endcode */ function entityreference_autocomplete_value_callback(array $element, $edit = FALSE) { // Just process the value when $edit comes as FALSE (no form submission). if (FALSE === $edit && !empty($element['#default_value'])) { - // If there are any default values, transform them into proper labels. - $labels = array(); + $default_values = $element['#default_value']; + $entity_labels = array(); + + // Check that value is an entity ID or entity itself. + $is_value_acceptable = function ($value) { + return is_numeric($value) || is_object($value); + }; + + // Check that value is a list of "entityreference_autocomplete" structures. + $is_list_of_values = function ($value) { + return is_array($value) && isset($value['entity_id']) && is_numeric($value['entity_id']); + }; + + // Single entity ID or entity object. + if ($is_value_acceptable($default_values)) { + $default_values = array($default_values); + } + // Not an array - good bay! + elseif (!is_array($default_values)) { + return $default_values; + } + elseif ($is_list_of_values($default_values)) { + $default_values = array($default_values['entity_id']); + } + + foreach ($default_values as $default_value) { + if ($is_list_of_values($default_value)) { + $default_value = $default_value['entity_id']; + } - foreach (is_array(reset($element['#default_value'])) ? $element['#default_value'] : array($element['#default_value']) as $value) { - $labels[] = entityreference_autocomplete_label_for_reference($value['entity_type'], $value['entity_id']); + if ($is_value_acceptable($default_value)) { + $entity_labels[] = entityreference_autocomplete_label_for_reference( + $element['#era_entity_type'], + $default_value + ); + } } - $labels = array_filter($labels); + $entity_labels = array_filter($entity_labels); // This should never be reached, but if it's, return whatever default // value was specified. - return empty($labels) ? $element['#default_value'] : implode(', ', $labels); + return empty($entity_labels) ? $default_values : implode(', ', $entity_labels); } // The user submitted a value for the element. Return it as is. @@ -187,7 +259,7 @@ function entityreference_autocomplete_validate_entityreference(array &$element, $query->entityCondition('entity_id', $matches[1]); } else { - $label_column = entityreference_autocomplete_resolve_entity_label_column($entity_type); + $label_column = entityreference_autocomplete_resolve_entity_label_column($entity_type, $entity_info); $query->propertyCondition($label_column, $entity_label); } @@ -249,7 +321,7 @@ function entityreference_autocomplete_validate_entityreference(array &$element, // User doesn't have read (view) access to it, set same error as the one // for no matches, since we don't want to reveal that the entity exists. - if (!entity_access('view', $entity_type, $entity)) { + if (!entityreference_autocomplete_entity_access('view', $entity_type, $entity, $entity_info)) { form_error($element, t('There are no entities matching "%value"', array( '%value' => $entity_label, ))); @@ -283,33 +355,35 @@ function entityreference_autocomplete_validate_entityreference(array &$element, * * @param string $entity_type * The type of the entity being referenced. - * @param string|int $entity_id + * @param string|int|object $entity * The ID of the Entity being referenced. * @param bool $quote_wrap * Whether the label should be wrapped within quotes if it contains commas or - * quotes. defaults to TRUE. + * quotes. Defaults to TRUE. * * @return string * The assembled label for the reference. */ -function entityreference_autocomplete_label_for_reference($entity_type, $entity_id, $quote_wrap = TRUE) { - if ($entity_referenced = entity_load_single($entity_type, $entity_id)) { - $reference_label = entity_label($entity_type, $entity_referenced) . " ($entity_id)"; - - // Names containing commas, preceding or trailing spaces, or quotes, must be - // wrapped in quotes. - if ( - $quote_wrap && - ( - strpos($reference_label, ',') !== FALSE || - strpos($reference_label, '"') !== FALSE || - trim($reference_label) !== $reference_label - ) - ) { - $reference_label = '"' . str_replace('"', '""', $reference_label) . '"'; - } +function entityreference_autocomplete_label_for_reference($entity_type, $entity, $quote_wrap = TRUE) { + if (is_numeric($entity)) { + $entity = entity_load($entity_type, array($entity)); + $entity = reset($entity); + } + + if (!empty($entity)) { + list($entity_id) = entity_extract_ids($entity_type, $entity); + + if (!empty($entity_id)) { + $label = entity_label($entity_type, $entity) . " ($entity_id)"; + + // Names containing commas, preceding or trailing spaces, or quotes, + // must be wrapped in quotes. + if ($quote_wrap && (strpos($label, ',') !== FALSE || strpos($label, '"') !== FALSE || trim($label) !== $label)) { + $label = '"' . str_replace('"', '""', $label) . '"'; + } - return $reference_label; + return $label; + } } // No entity object loaded, so return NULL. @@ -321,14 +395,19 @@ function entityreference_autocomplete_label_for_reference($entity_type, $entity_ * * @param string $entity_type * The entity type for which the column to use as label needs to be resolved. + * @param array $entity_info + * Entity definition, returned by "entity_get_info($entity_type)". * * @return string * The name of the column to use as the entity label for the passed entity. */ -function entityreference_autocomplete_resolve_entity_label_column($entity_type) { - $entity_info = entity_get_info($entity_type); +function entityreference_autocomplete_resolve_entity_label_column($entity_type, array $entity_info = array()) { $label_column = FALSE; + if (empty($entity_info)) { + $entity_info = entity_get_info($entity_type); + } + // Check if the entity has a label column defined. if (isset($entity_info['entity keys']['label'])) { $label_column = $entity_info['entity keys']['label']; @@ -398,3 +477,41 @@ function entityreference_autocomplete_explode_tags($tags, $remove_escape_formatt return $typed_tags; } + +/** + * Determines whether the given user can perform actions on an entity. + * + * Currently this is replacement of "entity_access()" from "entity" module. + * + * @param string $op + * The operation being performed. One of "view", "update", "create" or + * "delete". + * @param string $entity_type + * The type of entity to check for. + * @param object $entity + * Optionally an entity to check access for. If no entity is given, it will + * be determined whether access is allowed for all entities of the given type. + * @param array $entity_info + * Entity definition, returned by "entity_get_info($entity_type)". + * + * @return bool + * Whether access is allowed or not. If the entity type does not specify any + * access information, TRUE is returned. + */ +function entityreference_autocomplete_entity_access($op, $entity_type, $entity = NULL, array $entity_info = array()) { + if (empty($entity_info)) { + $entity_info = entity_get_info($entity_type); + } + + if (isset($entity_info['access callback'])) { + // Check an access for global user. + $account = NULL; + + return $entity_info['access callback']($op, $entity, $account, $entity_type); + } + + // The "access callback" property provided by "entity" module, but we are + // not going to be dependent from it just for using "entity_access()". Assume + // that access is allowed if no explicit checker for this is set. + return TRUE; +} diff --git a/includes/autocomplete_callback.inc b/includes/autocomplete_callback.inc index baf4e87..865558a 100644 --- a/includes/autocomplete_callback.inc +++ b/includes/autocomplete_callback.inc @@ -64,7 +64,7 @@ function entityreference_autocomplete_autocomplete_callback($entity_type, $bundl } // Fetch the column to use as label. - $label_column = entityreference_autocomplete_resolve_entity_label_column($entity_type); + $label_column = entityreference_autocomplete_resolve_entity_label_column($entity_type, $entity_info); $query->propertyCondition($label_column, $last_label, 'CONTAINS'); // Set property conditions, if any. @@ -109,10 +109,10 @@ function entityreference_autocomplete_autocomplete_callback($entity_type, $bundl // Iterate through all entities retrieved and process the data to return // it as expected by Drupal javascript. foreach ($entities as $entity_id => $entity) { - if (entity_access('view', $entity_type, $entity)) { + if (entityreference_autocomplete_entity_access('view', $entity_type, $entity, $entity_info)) { // Get the labels for the key and for the option. - $option = entityreference_autocomplete_label_for_reference($entity_type, $entity_id, FALSE); - $key = entityreference_autocomplete_label_for_reference($entity_type, $entity_id); + $option = entityreference_autocomplete_label_for_reference($entity_type, $entity, FALSE); + $key = entityreference_autocomplete_label_for_reference($entity_type, $entity); // $prefix . $key is the value that will be set in the textfield in // the browser, whereas $option is the html that is shown to the user diff --git a/tests/modules/entityreference_autocomplete_test/entityreference_autocomplete_test.info b/tests/modules/entityreference_autocomplete_test/entityreference_autocomplete_test.info new file mode 100644 index 0000000..a92810b --- /dev/null +++ b/tests/modules/entityreference_autocomplete_test/entityreference_autocomplete_test.info @@ -0,0 +1,5 @@ +name = Entity Reference Autocomplete (Test) +hidden = TRUE +core = 7.x + +dependencies[] = entityreference_autocomplete diff --git a/tests/modules/entityreference_autocomplete_test/entityreference_autocomplete_test.module b/tests/modules/entityreference_autocomplete_test/entityreference_autocomplete_test.module new file mode 100644 index 0000000..ed6d8a5 --- /dev/null +++ b/tests/modules/entityreference_autocomplete_test/entityreference_autocomplete_test.module @@ -0,0 +1,27 @@ + 'entityreference', + '#default_value' => $_SESSION['default_value'], + '#era_bundles' => isset($_SESSION['bundles']) ? $_SESSION['bundles'] : [], + '#era_entity_type' => $_SESSION['entity_type'], + '#era_cardinality' => isset($_SESSION['cardinality']) ? $_SESSION['cardinality'] : 1, + ); + + return $form; +} diff --git a/tests/src/BaseTest.inc b/tests/src/BaseTest.inc new file mode 100644 index 0000000..24536c9 --- /dev/null +++ b/tests/src/BaseTest.inc @@ -0,0 +1,75 @@ +drupalSetContent(drupal_render($form)); + } + +} diff --git a/tests/src/ValueCallbackTest.inc b/tests/src/ValueCallbackTest.inc new file mode 100644 index 0000000..2d9f6a6 --- /dev/null +++ b/tests/src/ValueCallbackTest.inc @@ -0,0 +1,117 @@ +drupalCreateUser(); + list($entity_id) = entity_extract_ids($entity_type, $entity); + + $entity2 = $this->drupalCreateUser(); + list($entity_id2) = entity_extract_ids($entity_type, $entity2); + + $structure = array( + 'entity_id' => $entity_id, + 'entity_type' => 'dummy', + ); + + $structure2 = array( + 'entity_id' => $entity_id2, + 'entity_type' => 'dummy', + ); + + // Test single-valued field. + foreach (array( + 'Try single entity ID' => $entity_id, + 'Try list of entity IDs' => array($entity_id), + 'Try single structure' => $structure, + 'Try an array of structures' => array($structure), + 'Try single entity' => $entity, + 'Try list of entities' => array($entity), + ) as $test_name => $default_value) { + $label = sprintf('%s (%s)', entity_label($entity_type, $entity), $entity_id); + + $this->buildForm($entity_type, $default_value); + $this->assertFieldByName('entityreference', $label, $test_name); + } + + // Test multivalued field. + foreach (array( + 'Try list of entity IDs (2 values)' => array($entity_id, $entity_id2), + 'Try an array of structures (2 values)' => array($structure, $structure2), + 'Try list of entities (2 values)' => array($entity, $entity2), + ) as $test_name => $default_value) { + $label = sprintf( + '%s (%s), %s (%s)', + entity_label($entity_type, $entity), + $entity_id, + entity_label($entity_type, $entity2), + $entity_id2 + ); + + $this->buildForm($entity_type, $default_value, 2); + $this->assertFieldByName('entityreference', $label, $test_name); + } + } + + /** + * Test non-acceptable values for element definition. + */ + public function testNonAcceptableValues() { + $entity_type = 'user'; + $entity_id = 129031290; + $entity = new \stdClass(); + + $structure = array( + 'entity_id' => $entity_id, + 'entity_type' => 'dummy', + ); + + $wrong_structure1 = array( + 'entity_1d' => $entity_id, + ); + + $wrong_structure2 = array( + // Error "Array to string conversion". + 'entity_id' => array($entity_id), + ); + + $wrong_structure3 = array( + // Error "Object of class stdClass could not be converted to string". + 'entity_id' => array($entity), + ); + + foreach (array( + 'Try single entity ID' => $entity_id, + 'Try list of entity IDs' => array($entity_id), + 'Try single, correct, structure' => $structure, + 'Try an array of correct structures' => array($structure), + 'Try single, wrong, structure #1' => $wrong_structure1, + 'Try an array of wrong structures #1' => array($wrong_structure1), + 'Try single, wrong, structure #2' => $wrong_structure2, + 'Try an array of wrong structures #2' => array($wrong_structure2), + 'Try single, wrong, structure #3' => $wrong_structure3, + 'Try an array of wrong structures #3' => array($wrong_structure3), + 'Try single entity' => $entity, + 'Try list of entities' => array($entity), + ) as $test_name => $default_value) { + $this->buildForm($entity_type, $default_value); + $this->assertFieldByName('entityreference', '', $test_name); + } + } + +}