'entityreference_autofill_form_autofill', ); } return; } } /** * Implements hook_field_attach_form(). */ function entityreference_autofill_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { // Get info about current entity. list(, , $bundle) = entity_extract_ids($entity_type, $entity); // Get array of fields that have enabled autofill. $autofill_settings = _entityreference_autofill_get_settings(); // No autofill references in this bundle. if (empty($autofill_settings[$entity_type][$bundle])) { return; } // Load settings from form_state. $autofill_bundle_settings = $autofill_settings[$entity_type][$bundle]; // If an AJAX-call triggered this request, // use the triggering field's settings to autofill. $callback = isset($form_state['triggering_element']['#ajax']['callback']) ? $form_state['triggering_element']['#ajax']['callback'] : FALSE; if ($callback == 'entityreference_autofill_form_autofill') { $triggering_element = $form_state['triggering_element']; $parents = $triggering_element['#field_parents']; if (isset($triggering_element['#field_name'])) { $reference_field_name = $triggering_element['#field_name']; } elseif (isset($triggering_element['#name'])) { // Try to fetch the fields name part from #name key // (#field_name is not available for radio widget). // // @todo Hacky solution, this (outer) condition should not be necessary, // find other way to get triggering field name? $field_name_end_pos = strpos($triggering_element['#name'], '['); if ($field_name_end_pos) { $reference_field_name = substr($triggering_element['#name'], 0, $field_name_end_pos); } } // Fill form with values from selected entity. if (isset($reference_field_name) && !empty($autofill_bundle_settings[$reference_field_name]['fields'])) { $reference_field_parents = $triggering_element['#parents']; $referenced_target_id = drupal_array_get_nested_value($form_state['values'], $reference_field_parents); // Get field and instance info for triggering element. // @todo Fetch from form_state instead? // In that case, field collections - how? $context_field = array(); $context_field['field'] = field_info_field($reference_field_name); if (isset($triggering_element['#entity_type'], $triggering_element['#bundle'])) { $context_field['instance'] = field_info_instance($triggering_element['#entity_type'], $reference_field_name, $triggering_element['#bundle']); } // For third-party widgets, reference target id might have to be fetched // from someplace else. Alter hook for module-specific customization. $context = array( 'field_name' => $reference_field_name, 'field' => $context_field, 'form' => $form, 'langcode' => $langcode, ); drupal_alter('entityreference_autofill_target_id', $referenced_target_id, $form_state, $context); // Feeble attempt to support arbitrary widgets. // (For radio support). while (is_array($referenced_target_id)) { $referenced_target_id = isset($referenced_target_id['target_id']) ? $referenced_target_id['target_id'] : reset($referenced_target_id); } entityreference_autofill_populate_form_by_field($referenced_target_id, $reference_field_name, $langcode, $autofill_bundle_settings[$reference_field_name], $entity_type, $bundle, $form, $form_state); } } // Set default value if relevant. // @todo is this condition sound? elseif (empty($form_state['input'])) { // Find all prepopulated reference fields in current bundle. foreach (array_keys($autofill_bundle_settings) as $reference_field_name) { // Check if field has default value. $language = field_language($entity_type, $entity, $reference_field_name, $langcode); if (empty($language)) { $language = LANGUAGE_NONE; } $field = field_info_field($reference_field_name); $instance = field_info_instance($entity_type, $reference_field_name, $bundle); $default_value = field_get_default_value($entity_type, $entity, $field, $instance, $language); // Fill form with referenced values. if (isset($default_value[0]['target_id'])) { $referenced_target_id = $default_value[0]['target_id']; entityreference_autofill_populate_form_by_field($referenced_target_id, $reference_field_name, $langcode, $autofill_bundle_settings[$reference_field_name], $entity_type, $bundle, $form, $form_state); } } } // Add autofill wrappers to enabled fields. foreach ($autofill_bundle_settings as $reference_field) { foreach ($reference_field['fields'] as $field_name) { $wrapper_id = _entityreference_autofill_get_wrapper($entity_type, $bundle, $field_name, $form['#parents']); $form[$field_name]['#prefix'] = '
'; $form[$field_name]['#suffix'] = '
'; } } } /** * AJAX callback for entity selection. * * @return array * Array of AJAX commands. */ function entityreference_autofill_form_autofill($form, $form_state) { // Need entity type to continue. if (!isset($form_state['triggering_element']['#entity_type'])) { return; } $entity_type = $form_state['triggering_element']['#entity_type']; $bundle = isset($form_state['triggering_element']['#bundle']) ? $form_state['triggering_element']['#bundle'] : $entity_type; if (!isset($form_state['entityreference_autofill'][$entity_type][$bundle])) { // Nothing to return. return; } // Load field names and their mapped wrapper ids. $parents = !empty($form_state['triggering_element']['#field_parents']) ? $form_state['triggering_element']['#field_parents'] : array(); $autofill_wrapper_map = drupal_array_get_nested_value($form_state['entityreference_autofill'][$entity_type][$bundle], $parents); // No values to fill. if (!$autofill_wrapper_map) { return; } // Build AJAX replace commands. $parents = $form_state['triggering_element']['#field_parents']; $field_parent = drupal_array_get_nested_value($form, $parents); $commands = array(); foreach ($autofill_wrapper_map as $field_name => $wrapper_id) { // Attach status messages to fields. if ($messages = theme('status_messages')) { $field_parent[$field_name]['messages'] = array( '#markup' => '
' . $messages . '
', ); } $commands[] = ajax_command_replace('#' . $wrapper_id, drupal_render($field_parent[$field_name])); } // Allow other modules to add additional // ajax commands to return on an autofill // callback. // @see entityreference_autofill.api.php $context = array( 'form' => $form, 'form_state' => $form_state, ); drupal_alter('entityreference_autofill_ajax_commands', $commands, $context); return array('#type' => 'ajax', '#commands' => $commands); } /** * Populate other form fields with respect to this module's field settings. * * @param int $referenced_target_id * id of referenced entity to fetch data from. * @param string $reference_field_name * name of entity reference field triggering this call. * @param string $langcode * current language. * @param array $autofill_field_settings * module settings for $reference_field_name in current context. * @param string $entity_type * the entity type of the referencing form. * @param string $bundle * the bundle of the referencing form * @param string $form * the $form array. * @param string $form_state * the $form_state array. */ function entityreference_autofill_populate_form_by_field($referenced_target_id, $reference_field_name, $langcode, $autofill_field_settings, $entity_type, $bundle, &$form, &$form_state) { // Entity reference field. $reference_field = field_form_get_state($form['#parents'], $reference_field_name, LANGUAGE_NONE, $form_state); // Reference entity metadata. $referenced_entity_type = $reference_field['field']['settings']['target_type']; // No value, quit processing. if (!is_numeric($referenced_target_id)) { return; } // Load referenced entity. $referenced_entity = entity_load_single($referenced_entity_type, $referenced_target_id); // Populate fields with values from referenced node. $autofill_fields = $autofill_field_settings['fields']; $overwrite = $autofill_field_settings['overwrite']; // Empty current form_state for this bundle. if (!isset($form_state['entityreference_autofill'][$entity_type][$bundle])) { $form_state['entityreference_autofill'][$entity_type][$bundle] = array(); } drupal_array_set_nested_value($form_state['entityreference_autofill'][$entity_type][$bundle], $form['#parents'], array()); $autofill_map = &drupal_array_get_nested_value($form_state['entityreference_autofill'][$entity_type][$bundle], $form['#parents']); // Load relevant part of form's input array. $form_input = &drupal_array_get_nested_value($form_state['input'], $form['#parents']); foreach ($autofill_fields as $field_name) { if (isset($form[$field_name])) { // Language fail-safe for referenced entity. // Use field language or undefined if empty. // Makes referenced values sensitive to language // selection in entity form. // @see https://drupal.org/node/2205245 $referenced_language = isset($form_state['values']['language']) ? $form_state['values']['language'] : $langcode; // Limit to allowed language(s) for this field, fallback to undefined. $referenced_language = field_language($referenced_entity_type, $referenced_entity, $field_name, $referenced_language); if (empty($referenced_language)) { $referenced_language = LANGUAGE_NONE; } // Get current field's language. $field_language = $form[$field_name]['#language']; // Consider overwrite setting. if (!$overwrite) { if (_entityreference_autofill_field_has_value($form_input[$field_name][$field_language], $field_name)) { continue; } } // Load new value. $items = field_get_items($referenced_entity_type, $referenced_entity, $field_name, $referenced_language); if (!empty($items)) { // Add callback info if AJAX. $autofill_map[$field_name] = _entityreference_autofill_get_wrapper($entity_type, $bundle, $field_name, $form['#parents']); // Field information for rendering form. $field = field_info_field($field_name); $instance = field_info_instance($entity_type, $field_name, $bundle); // Let other modules interact with $form_state prior to // generating this field's form. // @see entityreference_autofill.api.php $context = array( 'field' => $field, 'instance' => $instance, 'items' => $items, 'langcode' => $referenced_language, 'reference_field_name' => $reference_field_name, ); drupal_alter('entityreference_autofill_fill_items', $form_state, $context); // Replace field state with referenced values. // This is needed for multi-value fields with different // cardinality in source and destination. $field_state = field_form_get_state($form['#parents'], $field_name, $field_language, $form_state); $field_state['items_count'] = count($items); field_form_set_state($form['#parents'], $field_name, $field_language, $form_state, $field_state); // Replace field with new defaults. // @todo $referenced_language or $field_language here? // Implications if they differ? $field_form = field_default_form($referenced_entity_type, $referenced_entity, $field, $instance, $referenced_language, $items, $form, $form_state); $form[$field_name] = reset($field_form); // Unset current input to use new default values in form. unset($form_input[$field_name]); } } } } /** * Get wrapper id for a field. * * @param string $entity_type * form entity type. * @param string $bundle * entity bundle. * @param string $field_name * field name. * * @return string * field wrapper html id. */ function _entityreference_autofill_get_wrapper($entity_type, $bundle, $field_name, $parents = array()) { return 'entityreference-autofill-' . $entity_type . '-' . $bundle . '--' . implode('-', $parents) . '-' . $field_name; } /** * Widget form array paths keyed by widget type. * * This function defines which widgets are supported by the * module (by array keys). The values are the path in the * widget element where the AJAX callback is attached. * * @param string $widget_type * (optional) Widget type to get ajax base path for. * * @return array * Array of widget machine names that this module support, * or ajax base path if $widget_type is set. * * @see entityreference_autofill_field_widget_form_alter() */ function _entityreference_autofill_supported_widgets($widget_type = FALSE) { $supported_widgets = array( 'entityreference_autocomplete' => array('target_id'), 'options_select' => array(), 'options_buttons' => array(), ); // Allow other modules to add other widgets support. $additional_widgets = module_invoke_all('entityreference_autofill_supported_widgets'); $supported_widgets += $additional_widgets; if ($widget_type) { return isset($supported_widgets[$widget_type]) ? $supported_widgets[$widget_type] : FALSE; } return $supported_widgets; } /** * Check if a field has an input value. * * @param array $items * Array of items in the fields $form_state['input'] array. * @param string $field_name * The name of the field in question. * * @return bool * Whether or not a value is set for this field. */ function _entityreference_autofill_field_has_value($items, $field_name) { // Find out if field implement hook_field_is_empty. $field_info = field_info_field($field_name); $function = $field_info['module'] . '_field_is_empty'; // Check if empty. if (function_exists($function)) { foreach ($items as $item) { if (!$function($item, $field_info)) { return TRUE; } } return FALSE; } // Fallback to recursive array check. return _entityreference_autofill_field_array_value_exists($items); } /** * Recursive check if a field already has a set value. * * @param string $field_input * Array of field input values. * * @return bool * Wheter or not the field has a value set. * * @todo Not sure how good this works, neither if there's a * more efficient/clean way of doing it. */ function _entityreference_autofill_field_array_value_exists($field_input) { if (is_array($field_input)) { foreach ($field_input as $value) { if (_entityreference_autofill_field_array_value_exists($value)) { return TRUE; } } } else { return !empty($field_input); } } /** * Get settings for fields that have autofill enabled. * * @param bool $reset * Boolean indicating if the settings should be generated or * fetched from cache. * * @return array * Multidimensional map of entityreference fields, * keyed by (in order) entity type, bundle and field name. * A settings entry contains: * - overwrite: Boolean indicating if fields with values * should be overwritten. * - prepopulate: Available if the Entityreference prepopulate * module is enabled for this field. * - fields: Map of fields that should be filled by the module. */ function _entityreference_autofill_get_settings($reset = FALSE) { // Use cached if available // (Static is superfluous for now). $settings = &drupal_static(__FUNCTION__); if (!isset($settings) && !$reset) { $cache = cache_get(__FUNCTION__); if (!empty($cache->data)) { $settings = $cache->data; } } // Rebuild settings. if (!isset($settings) || $reset) { $field_map = field_info_field_map(); $enabled_fields = array(); foreach ($field_map as $field_name => $field) { if ($field['type'] !== 'entityreference') { continue; } foreach ($field['bundles'] as $entity_type => $bundles) { foreach ($bundles as $bundle) { $field_info = field_info_instance($entity_type, $field_name, $bundle); if (isset($field_info['settings']['behaviors']['autofill'])) { $module_settings = $field_info['settings']['behaviors']['autofill']; if ($module_settings['status']) { // Clear unused fields from field array. $module_settings['fields'] = array_filter($module_settings['fields']); $enabled_fields[$entity_type][$bundle][$field_name] = $module_settings; // Reduntant, module is enabled if key exists. unset($module_settings['status']); // Entityreference prepopulate is enabled. if (!empty($field_info['settings']['behaviors']['prepopulate']['status'])) { $enabled_fields[$entity_type][$bundle][$field_name]['prepopulate'] = TRUE; } } } } } } cache_set(__FUNCTION__, $enabled_fields); $settings = $enabled_fields; } return $settings; }