diff --git a/entityreference.handler.inc b/entityreference.handler.inc index e66b414..144b21c 100644 --- a/entityreference.handler.inc +++ b/entityreference.handler.inc @@ -12,9 +12,12 @@ interface EntityReferenceHandler { * * @param $field * A field datastructure. + * @param $instance + * A field instance datastructure. + * * @return EntityReferenceHandler */ - public static function getInstance($field); + public static function getInstance($field, $instance); /** * Return a list of referencable entities. @@ -22,12 +25,12 @@ interface EntityReferenceHandler { public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0); /** - * Count entities that are referencable by a given field. + * Count entities that are referencable by a given field instance. */ public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS'); /** - * Validate that entities can be referenced by this field. + * Validate that entities can be referenced by this field instance. * * @return * An array of entity ids that are valid. @@ -47,22 +50,23 @@ interface EntityReferenceHandler { /** * Generate a settings form for this handler. */ - public static function settingsForm($field); + public static function settingsForm($field, $instance); } /** * A null implementation of EntityReferenceHandler. */ class EntityReferenceHandler_broken implements EntityReferenceHandler { - public static function getInstance($field) { - return new EntityReferenceHandler_broken($field); + public static function getInstance($field, $instance) { + return new EntityReferenceHandler_broken($field, $instance); } - protected function __construct($field) { + protected function __construct($field, $instance) { $this->field = $field; + $this->instance = $instance; } - public static function settingsForm($field) { + public static function settingsForm($field, $instance) { $form['handler'] = array( '#markup' => t('The selected handler is broken.'), ); diff --git a/entityreference.info b/entityreference.info index b5e494a..1f27a57 100644 --- a/entityreference.info +++ b/entityreference.info @@ -1,6 +1,7 @@ name = Entity Reference description = Provides a field that can reference other entities. core = 7.x +dependencies[] = system (>7.8) dependencies[] = entity dependencies[] = ctools diff --git a/entityreference.module b/entityreference.module index 60230a7..891798a 100644 --- a/entityreference.module +++ b/entityreference.module @@ -25,6 +25,14 @@ function entityreference_field_info() { 'label' => t('Entity Reference'), 'description' => t('This field reference another entity.'), 'settings' => array( + // Field settings have been moved to instance settings, but keep them + // in here too in order to prevent things from blowing up before running + // the update function. + 'target_type' => '', + 'handler' => '', + 'handler_settings' => array(), + ), + 'instance_settings' => array( // The target entity type, pick node if it exists, or the first entity type if node. 'target_type' => ($entity_info = entity_get_info()) && isset($entity_info['node']) ? 'node' : key($entity_info), // The handler for this field. @@ -32,7 +40,6 @@ function entityreference_field_info() { // The handler settings. 'handler_settings' => array(), ), - 'instance_settings' => array(), 'default_widget' => 'entityreference_autocomplete', 'default_formatter' => 'entityreference_label', 'property_callbacks' => array('entityreference_field_property_callback'), @@ -76,16 +83,16 @@ function entityreference_field_is_empty($item, $field) { * * The handler contains most of the business logic of the field. */ -function entityreference_get_handler($field) { - $handler = $field['settings']['handler']; +function entityreference_get_handler($field, $instance) { + $handler = $instance['settings']['handler']; ctools_include('plugins'); $class = ctools_plugin_load_class('entityreference', 'handler', $handler, 'handler'); if (class_exists($class)) { - return call_user_func(array($class, 'getInstance'), $field); + return call_user_func(array($class, 'getInstance'), $field, $instance); } else { - return EntityReferenceHandler_broken::getInstance($field); + return EntityReferenceHandler_broken::getInstance($field, $instance); } } @@ -100,14 +107,14 @@ function entityreference_field_validate($entity_type, $entity, $field, $instance } } - $valid_ids = entityreference_get_handler($field)->validateReferencableEntities(array_keys($ids)); + $valid_ids = entityreference_get_handler($field, $instance)->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)), + 'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $instance['settings']['target_type'], '@id' => $id)), ); } } @@ -120,15 +127,15 @@ function entityreference_field_validate($entity_type, $entity, $field, $instance */ function entityreference_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { foreach ($items as $delta => $item) { - $items[$delta]['target_type'] = $field['settings']['target_type']; + $items[$delta]['target_type'] = $instance['settings']['target_type']; } } /** - * Implements hook_field_settings_form(). + * Implements hook_field_instance_settings_form(). */ -function entityreference_field_settings_form($field, $instance, $has_data) { - $settings = $field['settings']; +function entityreference_field_instance_settings_form($field, $instance) { + $settings = $instance['settings']; // Select the target entity type. $entity_type_options = array(); @@ -140,10 +147,10 @@ function entityreference_field_settings_form($field, $instance, $has_data) { '#type' => 'select', '#title' => t('Target type'), '#options' => $entity_type_options, - '#default_value' => $field['settings']['target_type'], + '#default_value' => $instance['settings']['target_type'], '#required' => TRUE, '#description' => t('The entity type that can be referenced thru this field.'), - '#disabled' => $has_data, + '#disabled' => _entityreference_field_instance_has_data($field, $instance), '#size' => 1, '#ajax' => array( 'callback' => 'entityreference_settings_ajax', @@ -188,6 +195,7 @@ function entityreference_field_settings_form($field, $instance, $has_data) { '#process' => array('entityreference_render_settings'), '#tree' => TRUE, '#field' => $field, + '#instance' => $instance, ); return $form; @@ -212,11 +220,12 @@ function entityreference_render_settings($element, $form_state) { // Rebuild the field configuration based on the submitted structure. $field = $element['#field']; - if (isset($form_state['values']['field']['settings'])) { - $field['settings'] = $form_state['values']['field']['settings'] + $field['settings']; + $instance = $element['#instance']; + if (isset($form_state['values']['instance']['settings'])) { + $instance['settings'] = $form_state['values']['instance']['settings'] + $instance['settings']; } - $element += call_user_func(array($class, 'settingsForm'), $field); + $element += call_user_func(array($class, 'settingsForm'), $field, $instance); return $element; } @@ -254,7 +263,7 @@ function entityreference_settings_ajax_submit($form, &$form_state) { */ function entityreference_field_property_callback(&$info, $entity_type, $field, $instance, $field_type) { // Set the property type based on the targe type. - $field_type['property_type'] = $field['settings']['target_type']; + $field_type['property_type'] = $instance['settings']['target_type']; // Then apply the default. entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type); @@ -341,22 +350,22 @@ function entityreference_field_widget_settings_form($field, $instance) { /** * Implements hook_options_list(). */ -function entityreference_options_list($field) { - return entityreference_get_handler($field)->getReferencableEntities(); +function entityreference_options_list($field, $instance) { + return entityreference_get_handler($field, $instance)->getReferencableEntities(); } /** * Implements hook_query_TAG_alter(). */ function entityreference_query_entityreference_alter(QueryAlterableInterface $query) { - entityreference_get_handler($query->getMetadata('field'))->entityFieldQueryAlter($query); + entityreference_get_handler($query->getMetadata('field'), $query->getMetadata('instance'))->entityFieldQueryAlter($query); } /** * Implements hook_field_widget_form(). */ function entityreference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { - $handler = entityreference_get_handler($field); + $handler = entityreference_get_handler($field, $instance); if ($instance['widget']['type'] == 'entityreference_autocomplete' || $instance['widget']['type'] == 'entityreference_autocomplete_tags') { @@ -380,7 +389,7 @@ function entityreference_field_widget_form(&$form, &$form_state, $field, $instan } // Load those entities and loop through them to extract their labels. - $entities = entity_load($field['settings']['target_type'], $entity_ids); + $entities = entity_load($instance['settings']['target_type'], $entity_ids); foreach ($entities as $entity_id => $entity) { $label = $handler->getLabel($entity); @@ -472,7 +481,7 @@ function entityreference_autocomplete_callback($type, $field_name, $entity_type, return MENU_ACCESS_DENIED; } - $handler = entityreference_get_handler($field); + $handler = entityreference_get_handler($field, $instance); if ($type == 'tags') { // The user enters a comma-separated list of tags. We only autocomplete the last tag. @@ -581,7 +590,7 @@ function entityreference_field_formatter_settings_summary($field, $instance, $vi } if ($display['type'] == 'entityreference_entity_view') { - $entity_info = entity_get_info($field['settings']['target_type']); + $entity_info = entity_get_info($instance['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'])); } @@ -602,12 +611,14 @@ function entityreference_field_formatter_prepare_view($entity_type, $entities, $ } if ($target_ids) { - $target_entities = entity_load($field['settings']['target_type'], $target_ids); - // Iterate through the fieldable entities again to attach the loaded data. foreach ($entities as $id => $entity) { $rekey = FALSE; + // We need to load the target entities for each entity because instances + // can have different target types. + $target_entities = entity_load($instances[$id]['settings']['target_type'], $target_ids); + foreach ($items[$id] as $delta => $item) { // Check whether the referenced entity could be loaded. if (isset($target_entities[$item['target_id']])) { @@ -637,13 +648,13 @@ function entityreference_field_formatter_view($entity_type, $entity, $field, $in switch ($display['type']) { case 'entityreference_label': - $handler = entityreference_get_handler($field); + $handler = entityreference_get_handler($field, $instance); foreach ($items as $delta => $item) { $label = $handler->getLabel($item['entity']); // 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($field['settings']['target_type'], $item['entity']))) { + if ($display['settings']['link'] && ($uri = entity_uri($instance['settings']['target_type'], $item['entity']))) { $result[$delta] = array('#markup' => l($label, $uri['path'], $uri['options'])); } else { @@ -663,7 +674,7 @@ function entityreference_field_formatter_view($entity_type, $entity, $field, $in $entity = clone $item['entity']; unset($entity->content); - $result[$delta] = entity_view($field['settings']['target_type'], array($item['target_id'] => $entity), $display['settings']['view_mode'], $langcode, FALSE); + $result[$delta] = entity_view($instance['settings']['target_type'], array($item['target_id'] => $entity), $display['settings']['view_mode'], $langcode, FALSE); $depth = 0; } break; @@ -673,6 +684,30 @@ function entityreference_field_formatter_view($entity_type, $entity, $field, $in } /** + * Determines whether a field instance has any data. + * + * @param $field + * A field structure. + * @param $instance + * An instance structure. + * + * @return + * TRUE if the field has data for any entity; FALSE otherwise. + * + * @see field_has_data() + */ +function _entityreference_field_instance_has_data($field, $instance) { + $query = new EntityFieldQuery(); + return (bool) $query + ->fieldCondition($field) + ->entityCondition('entity_type', $instance['entity_type']) + ->entityCondition('bundle', $instance['bundle']) + ->range(0, 1) + ->count() + ->execute(); +} + +/** * Exception thrown when the entity view renderer goes into a potentially infinite loop. */ class EntityReferenceRecursiveRenderingException extends Exception {} diff --git a/handler/base.inc b/handler/base.inc index 1a122df..2e310ff 100644 --- a/handler/base.inc +++ b/handler/base.inc @@ -17,25 +17,26 @@ class EntityReferenceHandler_base implements EntityReferenceHandler { /** * Implements EntityReferenceHandler::getInstance(). */ - public static function getInstance($field) { - $entity_type = $field['settings']['target_type']; + public static function getInstance($field, $instance) { + $entity_type = $instance['settings']['target_type']; if (class_exists($class_name = 'EntityReferenceHandler_' . $entity_type)) { - return new $class_name($field); + return new $class_name($field, $instance); } else { - return new EntityReferenceHandler_base($field); + return new EntityReferenceHandler_base($field, $instance); } } - protected function __construct($field) { + protected function __construct($field, $instance) { $this->field = $field; + $this->instance = $instance; } /** * Implements EntityReferenceHandler::settingsForm(). */ - public static function settingsForm($field) { - $entity_info = entity_get_info($field['settings']['target_type']); + public static function settingsForm($field, $instance) { + $entity_info = entity_get_info($instance['settings']['target_type']); $bundles = array(); foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) { $bundles[$bundle_name] = $bundle_info['label']; @@ -45,7 +46,7 @@ class EntityReferenceHandler_base implements EntityReferenceHandler { '#type' => 'select', '#title' => t('Target bundles'), '#options' => $bundles, - '#default_value' => isset($field['settings']['handler_settings']['target_bundles']) ? $field['settings']['handler_settings']['target_bundles'] : array(), + '#default_value' => isset($instance['settings']['handler_settings']['target_bundles']) ? $instance['settings']['handler_settings']['target_bundles'] : array(), '#size' => 6, '#multiple' => TRUE, '#description' => t('The bundles of the entity type that can be referenced. Optional, leave empty for all bundles.') @@ -58,7 +59,7 @@ class EntityReferenceHandler_base implements EntityReferenceHandler { */ public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { $options = array(); - $entity_type = $this->field['settings']['target_type']; + $entity_type = $this->instance['settings']['target_type']; $query = $this->buildEntityFieldQuery($match, $match_operator); if ($limit > 0) { @@ -92,7 +93,7 @@ class EntityReferenceHandler_base implements EntityReferenceHandler { */ public function validateReferencableEntities(array $ids) { if ($ids) { - $entity_type = $this->field['settings']['target_type']; + $entity_type = $this->instance['settings']['target_type']; $query = $this->buildEntityFieldQuery(); $query->entityCondition('entity_id', $ids, 'IN'); $result = $query->execute(); @@ -109,21 +110,22 @@ class EntityReferenceHandler_base implements EntityReferenceHandler { */ protected function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') { $query = new EntityFieldQuery(); - $query->entityCondition('entity_type', $this->field['settings']['target_type']); - if ($this->field['settings']['handler_settings']['target_bundles']) { - $query->entityCondition('bundle', $this->field['settings']['handler_settings']['target_bundles'], 'IN'); + $query->entityCondition('entity_type', $this->instance['settings']['target_type']); + if ($this->instance['settings']['handler_settings']['target_bundles']) { + $query->entityCondition('bundle', $this->instance['settings']['handler_settings']['target_bundles'], 'IN'); } if (isset($match)) { - $entity_info = entity_get_info($this->field['settings']['target_type']); + $entity_info = entity_get_info($this->instance['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($this->instance['settings']['target_type'] . '_access'); $query->addTag('entityreference'); $query->addMetaData('field', $this->field); + $query->addMetaData('instance', $this->instance); return $query; } @@ -160,7 +162,7 @@ class EntityReferenceHandler_base implements EntityReferenceHandler { * Implements EntityReferenceHandler::getLabel(). */ public function getLabel($entity) { - return entity_label($this->field['settings']['target_type'], $entity); + return entity_label($this->instance['settings']['target_type'], $entity); } } diff --git a/views/entityreference.views.inc b/views/entityreference.views.inc index 76cbc2b..83479aa 100644 --- a/views/entityreference.views.inc +++ b/views/entityreference.views.inc @@ -10,25 +10,38 @@ */ function entityreference_field_views_data($field) { $data = field_views_field_default_views_data($field); - $entity_info = entity_get_info($field['settings']['target_type']); - foreach ($data as $table_name => $table_data) { - if (isset($entity_info['base table'])) { - $entity = $entity_info['label']; - if ($entity == t('Node')) { - $entity = t('Content'); + + // Collect target type data for every field instance. + $entity_infos = array(); + foreach ($field['bundles'] as $entity_type => $entity_bundles) { + foreach ($entity_bundles as $entity_bundle) { + $instance = field_info_instance($entity_type, $field['field_name'], $entity_bundle); + if (!isset($entity_infos[$entity_type])) { + $entity_infos[$entity_type] = entity_get_info($instance['settings']['target_type']); } + } + } - $field_name = $field['field_name'] . '_target_id'; - $parameters = array('@entity' => $entity, '!field_name' => $field['field_name']); - $data[$table_name][$field_name]['relationship'] = array( - 'handler' => 'views_handler_relationship', - 'base' => $entity_info['base table'], - 'base field' => $entity_info['entity keys']['id'], - 'label' => t('@entity entity referenced from !field_name', $parameters), - 'group' => t('Entity Reference'), - 'title' => t('Referenced Entity'), - 'help' => t('A bridge to the @entity entity that is referenced via !field_name', $parameters), - ); + foreach ($entity_infos as $entity_info) { + foreach ($data as $table_name => $table_data) { + if (isset($entity_info['base table'])) { + $entity = $entity_info['label']; + if ($entity == t('Node')) { + $entity = t('Content'); + } + + $field_name = $field['field_name'] . '_target_id'; + $parameters = array('@entity' => $entity, '!field_name' => $field['field_name']); + $data[$table_name][$field_name]['relationship'] = array( + 'handler' => 'views_handler_relationship', + 'base' => $entity_info['base table'], + 'base field' => $entity_info['entity keys']['id'], + 'label' => t('@entity entity referenced from !field_name', $parameters), + 'group' => t('Entity Reference'), + 'title' => t('Referenced Entity'), + 'help' => t('A bridge to the @entity entity that is referenced via !field_name', $parameters), + ); + } } }