'node_reference autocomplete', 'page callback' => 'node_reference_autocomplete', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK ); return $items; } /** * Implement hook_theme(). * * @return array */ function node_reference_theme() { return array( 'node_reference_select' => array( 'render element' => 'element', ), 'node_reference_buttons' => array( 'render element' => 'element', ), 'node_reference_autocomplete' => array( 'render element' => 'element', ), 'field_formatter_node_reference_default' => array( 'render element' => 'element', ), 'field_formatter_node_reference_plain' => array( 'render element' => 'element', ), 'field_formatter_node_reference_full' => array( 'render element' => 'element', 'function' => 'theme_field_formatter_node_reference_node', ), 'field_formatter_node_reference_teaser' => array( 'render element' => 'element', 'function' => 'theme_field_formatter_node_reference_node', ), ); } /** * Implement hook_field_info(). * * @return array */ function node_reference_field_info() { return array( 'node_reference' => array( 'label' => t('Node reference'), 'description' => t('This field stores the ID of a related node as an integer value.'), 'settings' => array('referenceable_types' => array()), // It probably make more sense to have the referenceable types be per-field than per-instance // 'instance settings' => array('referenceable_types' => array()), 'default_widget' => 'node_reference_autocomplete', 'default_formatter' => 'node_reference_default', ), ); } /** * Implement hook_field_schema(). * * @return array */ function node_reference_field_schema($field) { $columns = array( 'nid' => array( 'type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, ), ); return array( 'columns' => $columns, 'indexes' => array('nid' => array('nid')), //useful to find back-references ); } /** * Implement hook_field_settings_form(). * * @param array $field * @param array $instance * @param boolean $has_data * @return array */ function node_reference_field_settings_form($field, $instance, $has_data) { $settings = $field['settings']; $form = array(); $form['referenceable_types'] = array( '#type' => 'checkboxes', '#title' => t('Content types that can be referenced'), '#multiple' => TRUE, '#default_value' => is_array($settings['referenceable_types']) ? $settings['referenceable_types'] : array(), '#options' => array_map('check_plain', node_type_get_names()), '#disabled' => $has_data, ); return $form; } /** * Implement of hook_field_instance_settings_form(). * * No per-instance settings in this version. * * @param array $field * @param array $instance * @return array */ function node_reference_field_instance_settings_form($field, $instance) { $form = array(); return $form; } /** * Implement hook_field_validate(). * * Possible error codes: * - 'valid_nid': nid is not a valid node id. Maybe it is even not a number. * * @param string $obj_type * @param object $object * @param array $field * @param array $instance * @param string $langcode * @param array $items * @param array $errors * @return void */ function node_reference_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) { // Extract nids to check. $ids = array(); // First check non-numeric "nid's to avoid losing time with them. foreach ($items as $delta => $item) { if (is_array($item) && !empty($item['nid'])) { if (is_numeric($item['nid'])) { $ids[] = $item['nid']; } else { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'valid_nid', 'message' => t("%name: invalid input.", array('%name' => t($field['widget']['label']))), ); } } } // Prevent performance hog if there are no ids to check. if ($ids) { $refs = _node_reference_potential_references($field, '', NULL, $ids); foreach ($items as $delta => $item) { if (is_array($item)) { if (!empty($item['nid']) && !isset($refs[$item['nid']])) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'valid_nid', 'message' => t("%name: this post can't be referenced.", array('%name' => t($field['widget']['label']))), ); } } } } } /** * Implement hook_field_load(). * * This hook can not be used to load the referenced node(s) because * nodes are fieldable entities, and this could cause infinite loops. * * @param string $obj_type * @param array $objects * @param array $field * @param array $instances * @param string $langcode * @param array $items * @param string $age * @return void */ function node_reference_field_load($obj_type, $objects, $field, $instances, $langcode, &$items, $age) { } /** * Implement hook_field_sanitize(). * * Since nids are numbers, there is nothing to sanitize about them. * * @param string $obj_type * @param object $object * @param array $field * @param arrya $instance * @param string $langcode * @param array $items * @return void */ function node_reference_field_sanitize($obj_type, $object, $field, $instance, $langcode, &$items) { } /** * Implement hook_field_is_empty(). * * @return boolean */ function node_reference_field_is_empty($item, $field) { // nid = 0 îs empty too, which is exactly what we want return empty($item['nid']) ? TRUE : FALSE; } /** * Implementation of hook_field_formatter_info(). * * @return array */ function node_reference_field_formatter_info() { return array( 'node_reference_default' => array( 'label' => t('Title (link)'), 'description' => t('Display the title of the referenced node as a link to the node page.'), 'field types' => array('node_reference'), // 'settings' => array(), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), ), 'node_reference_plain' => array( 'label' => t('Title (no link)'), 'description' => t('Display the title of the referenced node as plain text.'), 'field types' => array('node_reference'), // 'settings' => array(), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), ), 'node_reference_full' => array( 'label' => t('Full node'), 'description' => t('Display the title of the referenced node as a full node view.'), 'field types' => array('node_reference'), // 'settings' => array(), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), ), 'node_reference_teaser' => array( 'label' => t('Teaser'), 'description' => t('Display the title of the referenced node as a teaser node view.'), 'field types' => array('node_reference'), // 'settings' => array(), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, ), ), ); } /** * Theme function for 'default' node_reference field formatter. * * @param array $variables * @return string */ function theme_field_formatter_node_reference_default($variables) { $element = $variables['element']; if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid']) && ($title = _node_reference_titles($element['#item']['nid']))) { $output = l($title, 'node/'. $element['#item']['nid']); } else { $output = ''; } return $output; } /** * Theme function for 'plain' node_reference field formatter. * * @param array $variables * @return string */ function theme_field_formatter_node_reference_plain($variables) { $element = $variables['element']; if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid']) && ($title = _node_reference_titles($element['#item']['nid']))) { $output = check_plain($title); } else { $output = ''; } return $output; } /** * Proxy theme function for 'full' and 'teaser' node_reference field formatters. * * @param array $variables * @return string */ function theme_field_formatter_node_reference_node($variables) { $element = $variables['element']; static $recursion_queue = array(); $output = ''; if (!empty($element['#item']['nid']) && is_numeric($element['#item']['nid'])) { $node = $element['#object']; $field = field_info_field($element['#field_name']); // If no 'referencing node' is set, we are starting a new 'reference thread' if (!isset($node->referencing_node)) { $recursion_queue = array(); } $recursion_queue[] = $node->nid; if (in_array($element['#item']['nid'], $recursion_queue)) { // Prevent infinite recursion caused by reference cycles: // if the node has already been rendered earlier in this 'thread', // we fall back to 'default' (node title) formatter. return theme('field_formatter_node_reference_default', $element); } if ($referenced_node = node_load($element['#item']['nid'])) { $referenced_node->referencing_node = $node; $referenced_node->referencing_field = $field; _node_reference_titles($element['#item']['nid'], $referenced_node->title); // other values: 'node_reference_teaser' $build_mode = $element['#formatter'] == 'node_reference_full' ? 'full' : 'teaser'; $output = node_build($referenced_node, $build_mode); $output = drupal_render($output); } } return $output; } /** * Helper function for formatters. * * Store node titles collected in the curent request. */ function _node_reference_titles($nid, $known_title = NULL) { static $titles = array(); if (!isset($titles[$nid])) { if ($known_title) { $title = $known_title; } else { $q = db_select('node', 'n'); $node_title_alias = $q->addField('n', 'title'); $q->addTag('node_access') ->condition('n.nid', $nid) ->range(0, 1); $result = $q->execute(); $title = $result->fetchField(); } $titles[$nid] = $title ? $title : ''; } return $titles[$nid]; } /** * Implementation of hook_field_widget_info(). * * We need custom handling of multiple values for the node_reference_select * widget because we need to combine them into a options list rather * than display multiple elements. * * We will use the Field module's default handling for default value. * * Callbacks can be omitted if default handing is used. * They're included here just so this module can be used * as an example for custom modules that might do things * differently. */ function node_reference_field_widget_info() { return array( 'node_reference_select' => array( 'label' => t('Select list'), 'description' => t('Display the list of referenceable nodes in a SELECT.'), 'field types' => array('node_reference'), 'settings' => array( 'autocomplete_match' => 'contains', ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, 'default value' => FIELD_BEHAVIOR_DEFAULT, ), ), 'node_reference_buttons' => array( 'label' => t('Check boxes/radio buttons'), 'description' => t('Display the list of referenceable nodes as a set of (radio) boxes.'), 'field types' => array('node_reference'), 'settings' => array( 'autocomplete_match' => 'contains', ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, 'default value' => FIELD_BEHAVIOR_DEFAULT, ), ), 'node_reference_autocomplete' => array( 'label' => t('Autocomplete text field'), 'description' => t('Display the list of referenceable nodes as a textfield with autocomplete behaviour.'), 'field types' => array('node_reference'), 'settings' => array( 'autocomplete_match' => 'contains', 'size' => 60, ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_DEFAULT, 'default value' => FIELD_BEHAVIOR_DEFAULT, ), ), ); } /** * Implement hook_field_widget_settings_form(). * * @return array */ function node_reference_field_widget_settings_form($field, $instance) { $form = array(); $widget = $instance['widget']; $defaults = field_info_widget_settings($widget['type']); $settings = array_merge($defaults, $widget['settings']); if ($widget['type'] == 'node_reference_autocomplete') { $form['autocomplete_match'] = array( '#type' => 'select', '#title' => t('Autocomplete matching'), '#default_value' => $settings['autocomplete_match'], '#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('_element_validate_integer_positive'), '#required' => TRUE, ); } return $form; } /** * Implementation of FAPI hook_element_info(). * * Any FAPI callbacks needed for individual widgets can be declared here, * and the element will be passed to those callbacks for processing. * * Drupal will automatically theme the element using a theme with * the same name as the hook_elements key. * * Autocomplete_path is not used by text_widget but other widgets can use it * (see node_reference and userreference). */ function node_reference_element_info() { return array( 'node_reference_select' => array( '#input' => TRUE, '#columns' => array('uid'), '#delta' => 0, '#process' => array('node_reference_select_process'), ), 'node_reference_buttons' => array( '#input' => TRUE, '#columns' => array('uid'), '#delta' => 0, '#process' => array('node_reference_buttons_process'), ), 'node_reference_autocomplete' => array( '#input' => TRUE, '#columns' => array('name'), '#delta' => 0, '#process' => array('node_reference_autocomplete_process'), '#autocomplete_path' => FALSE, ), ); } /** * Implementation of hook_field_widget(). * * Attach a single form element to the form. It will be built out and * validated in the callback(s) listed in hook_elements. We build it * out in the callbacks rather than here in hook_widget so it can be * plugged into any module that can provide it with valid * $field information. * * Field module will set the weight, field name and delta values * for each form element. * * If there are multiple values for this field, the field module will * call this function as many times as needed. * * @param $form * the entire form array, $form['#node'] holds node information * @param $form_state * the form_state, $form_state['values'][$field['field_name']] * holds the field's form values. * @param $field * The field structure. * @param $instance * the field instance array * @param $items * array of default values for this field * @param $delta * the order of this item in the array of subelements (0, 1, 2, etc) * @param $element * * @return * the form item for a single element for this field */ function node_reference_field_widget(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { switch ($instance['widget']['type']) { case 'node_reference_select': $element += array( '#type' => 'node_reference_select', '#default_value' => $items, ); break; case 'node_reference_buttons': $element += array( '#type' => 'node_reference_buttons', '#default_value' => $items, ); break; case 'node_reference_autocomplete': $element += array( '#type' => 'node_reference_autocomplete', '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, '#value_callback' => 'node_reference_autocomplete_value', ); break; } return $element; } /** * Value for a node_reference autocomplete element. * * Substitute in the node title for the node nid. */ function node_reference_autocomplete_value($element, $edit = FALSE, $form_state) { $field_key = $element['#columns'][0]; if (!empty($element['#default_value'][$field_key])) { $nid = $element['#default_value'][$field_key]; $q = db_select('node', 'n'); $node_title_alias = $q->addField('n', 'title'); $q->addTag('node_access') ->condition('n.nid', $nid) ->range(0, 1); $result = $q->execute(); $value = $result->fetchField(); $value .= ' [nid:'. $nid .']'; } else { $value = NULL; } $ret = array($field_key => $value); return $ret; } /** * Validate an autocomplete element. * * Remove the wrapper layer and set the right element's value. * This will move the nested value at 'field-name-0-nid-nid' * back to its original location, 'field-name-0-nid'. */ function node_reference_autocomplete_validate($element, &$form_state, $form) { $field_key = $element['#columns'][0]; $value = $element['#value'][$field_key]; $nid = NULL; if (!empty($value)) { $field_name = $element['#field_name']; $instance = field_info_instance('node', $field_name, $element['#bundle']); preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $value, $matches); if (!empty($matches)) { // Explicit [nid:n]. list(, $title, $nid) = $matches; if (!empty($title) && ($n = node_load($nid)) && $title != $n->title[FIELD_LANGUAGE_NONE][0]['value']) { form_error($element[$field_key], t('%name: title mismatch. Please check your selection.', array('%name' => t($instance['label'])))); } } else { // No explicit nid. $field = field_info_field($field_name); $reference = _node_reference_potential_references($field, $value, 'equals', NULL, 1); if (empty($reference)) { form_error($element[$field_key], t('%name: found no valid post with that title.', array('%name' => t($instance['label'])))); } else { // TODO: the best thing would be to present the user with an // additional form, allowing the user to choose between valid // candidates with the same title. ATM, we pick the first // matching candidate... $nid = key($reference); } } } form_set_value($element, $nid, $form_state); } /** * Implement hook_field_widget_error(). * * @param array $element * @param array $error * @return void */ function nodereference_field_widget_error($element, $error) { $field_key = $element['#columns'][0]; form_error($element[$field_key], $error['message']); } /** * Process an individual element. * * Build the form element. When creating a form using FAPI #process, * note that $element['#value'] is already set. * * The $field and $instance arrays are in * $form['#fields'][$element['#field_name']]. * * The node_reference_select widget doesn't need to create its own * element: it can wrap around the options_select element. This will * create a new, nested instance of the field. Adding an element * validation step allows the value to be unwrapped. */ function node_reference_select_process($element, $form_state, $form) { $field_key = $element['#columns'][0]; $element[$field_key] = array( '#type' => 'options_select', '#default_value' => isset($element['#value']) ? $element['#value'] : '', // The following values were set by the field module and need // to be passed down to the nested element. '#object_type' => $element['#object_type'], '#title' => $element['#title'], '#required' => $element['#required'], '#description' => $element['#description'], '#field_name' => $element['#field_name'], '#bundle' => $element['#bundle'], '#delta' => $element['#delta'], '#columns' => $element['#columns'], ); if (empty($element[$field_key]['#element_validate'])) { $element[$field_key]['#element_validate'] = array(); } // Our validator needs to come first to unwrap the element before // other validators can act. array_unshift($element[$field_key]['#element_validate'], 'node_reference_options_validate'); return $element; } /** * Process an individual element. * * Build the form element. When creating a form using FAPI #process, * note that $element['#value'] is already set. * * The $field and $instance arrays are in * $form['#fields'][$element['#field_name']]. * * The node_reference_select widget doesn't need to create its own * element: it can wrap around the options_buttons element. This will * create a new, nested instance of the field. Adding an element * validation step where allows the value to be unwrapped. */ function node_reference_buttons_process($element, $form_state, $form) { $field_key = $element['#columns'][0]; $element[$field_key] = array( '#type' => 'options_buttons', '#default_value' => isset($element['#value']) ? $element['#value'] : '', // The following values were set by the field module and need // to be passed down to the nested element. '#object_type' => $element['#object_type'], '#title' => $element['#title'], '#required' => $element['#required'], '#description' => $element['#description'], '#field_name' => $element['#field_name'], '#bundle' => $element['#bundle'], '#delta' => $element['#delta'], '#columns' => $element['#columns'], ); if (empty($element[$field_key]['#element_validate'])) { $element[$field_key]['#element_validate'] = array(); } // Our validator needs to come first to unwrap the element before // other validators can act. array_unshift($element[$field_key]['#element_validate'], 'node_reference_options_validate'); return $element; } /** * Process an individual element. * * Build the form element. When creating a form using FAPI #process, * note that $element['#value'] is already set. * * The node_reference autocomplete widget doesn't need to create its own * element: it can wrap around the text_textfield element and add an * autocomplete path and some extra processing to it. Adding an element * validation step allows the value to be unwrapped. */ function node_reference_autocomplete_process($element, $form_state, $form) { $field_key = $element['#columns'][0]; $element[$field_key] = array( '#type' => 'text_textfield', '#default_value' => isset($element['#value']) ? $element['#value'] : '', '#autocomplete_path' => 'node_reference/autocomplete/' . $element['#field_name'], // The following values were set by the field module and need // to be passed down to the nested element. '#object_type' => $element['#object_type'], '#title' => $element['#title'], '#required' => $element['#required'], '#description' => $element['#description'], '#field_name' => $element['#field_name'], '#bundle' => $element['#bundle'], '#delta' => $element['#delta'], '#columns' => $element['#columns'], ); if (empty($element[$field_key]['#element_validate'])) { $element[$field_key]['#element_validate'] = array(); } // Our validator needs to come first to unwrap the element before // other validators can act. array_unshift($element[$field_key]['#element_validate'], 'node_reference_autocomplete_validate'); return $element; } /** * Validate a select/buttons element. * * Actually, this is not a validation layer, but uses the validation * process to remove the wrapper layer and set the right element's * value. We don't know exactly where this element is, so we drill down * through the element until we get to our key. * * We use $form_state['values'] instead of $element['#value'] * to be sure we have the most accurate value when other modules * like options are using #element_validate to alter the value. */ function node_reference_options_validate($element, &$form_state, $form) { $field_key = $element['#columns'][0]; $value = $form_state['values']; $new_parents = array(); foreach ($element['#parents'] as $parent) { $value = $value[$parent]; // Use === to be sure we get right results if parent is a zero (delta) value. if ($parent === $field_key) { $element['#parents'] = $new_parents; form_set_value($element, $value, $form_state); break; } $new_parents[] = $parent; } } /** * Fetch an array of all candidate referenced nodes. * * This info is used in various places (allowed values, autocomplete * results, input validation...). Some of them only need the nids, * others nid + titles, others yet nid + titles + rendered row (for * display in widgets). * * The array we return contains all the potentially needed information, * and lets consumers use the parts they actually need. * * @param $field * The field description. * @param $string * Optional string to filter titles on (used by autocomplete). * @param $match * Operator to match filtered name against, can be any of: * 'contains', 'equals', 'starts_with' * @param $ids * Optional node ids to lookup (the $string and $match arguments will be * ignored). * @param $limit * If non-zero, limit the size of the result set. * * @return * An array of valid nodes in the form: * array( * nid => array( * 'title' => The node title, * 'rendered' => The text to display in widgets (can be HTML) * ), * ... * ) */ function _node_reference_potential_references($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { static $results = array(); // Create unique id for static cache. $cid = $field['field_name'] . ':' . $match . ':' . ($string !== '' ? $string : implode('-', $ids)) . ':' . $limit; if (!isset($results[$cid])) { $references = _node_reference_potential_references_standard($field, $string, $match, $ids, $limit); // Store the results. $results[$cid] = !empty($references) ? $references : array(); } return $results[$cid]; } /** * Helper function for _node_reference_potential_references(): * referenceable nodes defined by content types. */ function _node_reference_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { // Avoid useless work if (!count($field['settings']['referenceable_types'])) { return array(); } $q = db_select('node', 'n'); $node_nid_alias = $q->addField('n', 'nid'); $node_title_alias = $q->addField('n', 'title', 'node_title'); $node_type_alias = $q->addField('n', 'type', 'node_type'); $q->addTag('node_access'); if (is_array($field['settings']['referenceable_types'])) { $q->condition('n.type', $field['settings']['referenceable_types'], 'IN'); } if ($string !== '') { $args = array(); switch ($match) { case 'contains': $title_clause = 'n.title LIKE :match'; $args['match'] = '%' . $string . '%'; break; case 'starts_with': $title_clause = 'n.title LIKE :match'; $args['match'] = $string . '%'; break; case 'equals': default: // no match type or incorrect match type: use "=" $title_clause = 'n.title = :match'; $args['match'] = $string; break; } $q->where($title_clause, $args); } elseif ($ids) { $q = $q->condition($node_nid_alias, $ids, 'IN', $ids); } $q->orderBy($node_title_alias) ->orderBy($node_type_alias); if ($limit) { $q->range(0, $limit); } $result = $q->execute(); $references = array(); foreach ($result->fetchAll() as $node) { $references[$node->nid] = array( 'title' => $node->node_title, 'rendered' => check_plain($node->node_title), ); } return $references; } /** * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users */ function node_reference_autocomplete($field_name, $string = '') { $fields = field_info_fields(); // content_fields(); $field = $fields[$field_name]; $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains'; $matches = array(); $references = _node_reference_potential_references($field, $string, $match, array(), 10); foreach ($references as $id => $row) { // Add a class wrapper for a few required CSS overrides. $matches[$row['title'] ." [nid:$id]"] = '
'. $row['rendered'] . '
'; } drupal_json_output($matches); } /** * Implementation of hook_node_type_update. * * Reflect type name changes to the 'referenceable types' settings: when * the name of a type changes, the change needs to be reflected in the * "referenceable types" setting for any node_reference field * referencing it. * * @param object $info * @return void */ function node_reference_node_type_update($info) { if (!empty($info->old_type) && $info->old_type != $info->type) { $fields = field_info_fields(); foreach ($fields as $field_name => $field) { if ($field['type'] == 'node_reference' && isset($field['settings']['referenceable_types'][$info->old_type])) { $field['settings']['referenceable_types'][$info->type] = empty($field['settings']['referenceable_types'][$info->old_type]) ? 0 : $info->type; unset($field['settings']['referenceable_types'][$info->old_type]); field_update_field($field); } } } } /** * Theme preprocess function. * * Allows specific node templates for nodes displayed as values of a * node_reference field with the 'full node' / 'teaser' formatters. */ function node_reference_preprocess_node(&$vars) { // The 'referencing_field' attribute of the node is added by the 'teaser' // and 'full node' formatters. if (!empty($vars['node']->referencing_field)) { $node = $vars['node']; $field = $node->referencing_field; $vars['template_files'][] = 'node-node_reference'; $vars['template_files'][] = 'node-node_reference-' . $field['field_name']; $vars['template_files'][] = 'node-node_reference-' . $node->type; $vars['template_files'][] = 'node-node_reference-' . $field['field_name'] .'-'. $node->type; } } /** * FAPI theme for an individual elements. * * The textfield or select is already rendered by the * textfield or select themes and the html output * lives in $element['#children']. Override this theme to * make custom changes to the output. * * $element['#field_name'] contains the field name * $element['#delta] is the position of this element in the group */ function theme_node_reference_select($variables) { $element = $variables['element']; return $element['#children']; } function theme_node_reference_buttons($variables) { $element = $variables['element']; return $element['#children']; } function theme_node_reference_autocomplete($variables) { $element = $variables['element']; return $element['#children']; } /** * Implementation of hook_field_prepare_translation(). * * When preparing a translation, load any translations of existing * references. * TODO: Core doc: "This hook may or may not survive in Field API". * So it is currently not verified. */ function node_reference_field_prepare_translation($obj_type, $object, $field, $instance, $langcode, &$items) { $addition = array(); $addition[$field['field_name']] = array(); if (isset($object->translation_source->$field['field_name']) && is_array($object->translation_source->$field['field_name'])) { foreach ($object->translation_source->$field['field_name'] as $key => $reference) { $reference_node = node_load($reference['nid']); // Test if the referenced node type is translatable and, if so, // load translations if the reference is not for the current language. // We can assume the translation module is present because it invokes 'prepare translation'. if (translation_supported_type($reference_node->type) && !empty($reference_node->language) && $reference_node->language != $node->language && $translations = translation_node_get_translations($reference_node->tnid)) { // If there is a translation for the current language, use it. $addition[$field['field_name']][] = array( 'nid' => isset($translations[$node->language]) ? $translations[$node->language]->nid : $reference['nid'], ); } } } return $addition; } /** * Implementation of the pseudo-hook "hook_allowed_values()". * * @see options_options() * @link http://drupal.org/node/639466 @endlink * * A problem with this function is that its result set can be as large * as the whole set of nodes on a site, which can be huge. * * @param array $field */ function node_reference_allowed_values($field) { $references = _node_reference_potential_references($field); $options = array(); foreach ($references as $key => $value) { $options[$key] = $value['rendered']; } return $options; }