danielebarchiesi@4: 'node_reference_autocomplete', danielebarchiesi@4: 'page arguments' => array(2, 3, 4), danielebarchiesi@4: 'access callback' => 'reference_autocomplete_access', danielebarchiesi@4: 'access arguments' => array(2, 3, 4), danielebarchiesi@4: 'type' => MENU_CALLBACK, danielebarchiesi@4: ); danielebarchiesi@4: return $items; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_info(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_info() { danielebarchiesi@4: return array( danielebarchiesi@4: 'node_reference' => array( danielebarchiesi@4: 'label' => t('Node reference'), danielebarchiesi@4: 'description' => t('This field stores the ID of a related node as an integer value.'), danielebarchiesi@4: 'settings' => array( danielebarchiesi@4: 'referenceable_types' => array(), danielebarchiesi@4: 'view' => array( danielebarchiesi@4: 'view_name' => '', danielebarchiesi@4: 'display_name' => '', danielebarchiesi@4: 'args' => array(), danielebarchiesi@4: ), danielebarchiesi@4: ), danielebarchiesi@4: // It probably make more sense to have the referenceable types be per-field than per-instance danielebarchiesi@4: // 'instance settings' => array('referenceable_types' => array()), danielebarchiesi@4: 'default_widget' => 'options_select', // node_reference_autocomplete', danielebarchiesi@4: 'default_formatter' => 'node_reference_default', danielebarchiesi@4: // Support hook_entity_property_info() from contrib "Entity API". danielebarchiesi@4: 'property_type' => 'node', danielebarchiesi@4: // Support default token formatter for field tokens. danielebarchiesi@4: 'default_token_formatter' => 'node_reference_plain', danielebarchiesi@4: ), danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_settings_form(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_settings_form($field, $instance, $has_data) { danielebarchiesi@4: $settings = $field['settings']; danielebarchiesi@4: danielebarchiesi@4: $form = array(); danielebarchiesi@4: $form['referenceable_types'] = array( danielebarchiesi@4: '#type' => 'checkboxes', danielebarchiesi@4: '#title' => t('Content types that can be referenced'), danielebarchiesi@4: '#multiple' => TRUE, danielebarchiesi@4: '#default_value' => $settings['referenceable_types'], danielebarchiesi@4: '#options' => array_map('check_plain', node_type_get_names()), danielebarchiesi@4: ); danielebarchiesi@4: danielebarchiesi@4: if (module_exists('views')) { danielebarchiesi@4: $view_settings = $settings['view']; danielebarchiesi@4: danielebarchiesi@4: $description = '

' . t('The list of nodes that can be referenced can provided by a view (Views module) using the "References" display type.') . '

'; danielebarchiesi@4: danielebarchiesi@4: // Special note for legacy fields migrated from D6. danielebarchiesi@4: if (!empty($view_settings['view_name']) && $view_settings['display_name'] == 'default') { danielebarchiesi@4: $description .= '

'. t("Important D6 migration note:") . ''; danielebarchiesi@4: $description .= '
' . t("The field is currently configured to use the 'Master' display of the view %view_name.", array('%view_name' => $view_settings['view_name'])); danielebarchiesi@4: $description .= '
' . t("It is highly recommended that you:
- edit this view and create a new display using the 'References' display type,
- update the field settings to explicitly select the correct view and display."); danielebarchiesi@4: $description .= '
' . t("The field will work correctly until then, but submitting this form might inadvertently change the field settings.") . '

'; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $form['view'] = array( danielebarchiesi@4: '#type' => 'fieldset', danielebarchiesi@4: '#title' => t('Views - Nodes that can be referenced'), danielebarchiesi@4: '#collapsible' => TRUE, danielebarchiesi@4: '#collapsed' => empty($view_settings['view_name']), danielebarchiesi@4: '#description' => $description, danielebarchiesi@4: ); danielebarchiesi@4: danielebarchiesi@4: $views_options = references_get_views_options('node'); danielebarchiesi@4: if ($views_options) { danielebarchiesi@4: // The value of the 'view_and_display' select below will need to be split danielebarchiesi@4: // into 'view_name' and 'view_display' in the final submitted values, so danielebarchiesi@4: // we massage the data at validate time on the wrapping element (not danielebarchiesi@4: // ideal). danielebarchiesi@4: $form['view']['#element_validate'] = array('_node_reference_view_settings_validate'); danielebarchiesi@4: danielebarchiesi@4: $views_options = array('' => '<' . t('none') . '>') + $views_options; danielebarchiesi@4: $default = empty($view_settings['view_name']) ? '' : $view_settings['view_name'] . ':' .$view_settings['display_name']; danielebarchiesi@4: $form['view']['view_and_display'] = array( danielebarchiesi@4: '#type' => 'select', danielebarchiesi@4: '#title' => t('View used to select the nodes'), danielebarchiesi@4: '#options' => $views_options, danielebarchiesi@4: '#default_value' => $default, danielebarchiesi@4: '#description' => '

' . t('Choose the view and display that select the nodes that can be referenced.
Only views with a display of type "References" are eligible.') . '

' . danielebarchiesi@4: t('Note:'), danielebarchiesi@4: ); danielebarchiesi@4: danielebarchiesi@4: $default = implode(', ', $view_settings['args']); danielebarchiesi@4: $form['view']['args'] = array( danielebarchiesi@4: '#type' => 'textfield', danielebarchiesi@4: '#title' => t('View arguments'), danielebarchiesi@4: '#default_value' => $default, danielebarchiesi@4: '#required' => FALSE, danielebarchiesi@4: '#description' => t('Provide a comma separated list of arguments to pass to the view.'), danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: $form['view']['no_view_help'] = array( danielebarchiesi@4: '#markup' => '

' . t('No eligible view was found.') .'

', danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return $form; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Validate callback for the 'view settings' fieldset. danielebarchiesi@4: * danielebarchiesi@4: * Puts back the various form values in the expected shape. danielebarchiesi@4: */ danielebarchiesi@4: function _node_reference_view_settings_validate($element, &$form_state, $form) { danielebarchiesi@4: // Split view name and display name from the 'view_and_display' value. danielebarchiesi@4: if (!empty($element['view_and_display']['#value'])) { danielebarchiesi@4: list($view, $display) = explode(':', $element['view_and_display']['#value']); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: $view = ''; danielebarchiesi@4: $display = ''; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Explode the 'args' string into an actual array. Beware, explode() turns an danielebarchiesi@4: // empty string into an array with one empty string. We'll need an empty array danielebarchiesi@4: // instead. danielebarchiesi@4: $args_string = trim($element['args']['#value']); danielebarchiesi@4: $args = ($args_string === '') ? array() : array_map('trim', explode(',', $args_string)); danielebarchiesi@4: danielebarchiesi@4: $value = array('view_name' => $view, 'display_name' => $display, 'args' => $args); danielebarchiesi@4: form_set_value($element, $value, $form_state); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_validate(). danielebarchiesi@4: * danielebarchiesi@4: * Possible error codes: danielebarchiesi@4: * - 'invalid_nid': nid is not valid for the field (not a valid node id, or the node is not referenceable). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { danielebarchiesi@4: // Extract nids to check. danielebarchiesi@4: $ids = array(); danielebarchiesi@4: danielebarchiesi@4: // First check non-numeric "nid's to avoid losing time with them. danielebarchiesi@4: foreach ($items as $delta => $item) { danielebarchiesi@4: if (is_array($item) && !empty($item['nid'])) { danielebarchiesi@4: if (is_numeric($item['nid'])) { danielebarchiesi@4: $ids[] = $item['nid']; danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: $errors[$field['field_name']][$langcode][$delta][] = array( danielebarchiesi@4: 'error' => 'invalid_nid', danielebarchiesi@4: 'message' => t("%name: invalid input.", danielebarchiesi@4: array('%name' => $instance['label'])), danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: // Prevent performance hog if there are no ids to check. danielebarchiesi@4: if ($ids) { danielebarchiesi@4: $options = array( danielebarchiesi@4: 'ids' => $ids, danielebarchiesi@4: ); danielebarchiesi@4: $refs = node_reference_potential_references($field, $options); danielebarchiesi@4: foreach ($items as $delta => $item) { danielebarchiesi@4: if (is_array($item)) { danielebarchiesi@4: if (!empty($item['nid']) && !isset($refs[$item['nid']])) { danielebarchiesi@4: $errors[$field['field_name']][$langcode][$delta][] = array( danielebarchiesi@4: 'error' => 'invalid_nid', danielebarchiesi@4: 'message' => t("%name: this post can't be referenced.", danielebarchiesi@4: array('%name' => $instance['label'])), danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_prepare_view(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) { danielebarchiesi@4: $checked_ids = &drupal_static(__FUNCTION__, array()); danielebarchiesi@4: danielebarchiesi@4: // Set an 'access' property on each item (TRUE if the node exists and is danielebarchiesi@4: // accessible by the current user). danielebarchiesi@4: danielebarchiesi@4: // Extract ids to check. danielebarchiesi@4: $ids = array(); danielebarchiesi@4: foreach ($items as $id => $entity_items) { danielebarchiesi@4: foreach ($entity_items as $delta => $item) { danielebarchiesi@4: if (is_array($item)) { danielebarchiesi@4: // Default to 'not accessible'. danielebarchiesi@4: $items[$id][$delta]['access'] = FALSE; danielebarchiesi@4: if (!empty($item['nid']) && is_numeric($item['nid'])) { danielebarchiesi@4: $ids[$item['nid']] = $item['nid']; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: if ($ids) { danielebarchiesi@4: // Load information about ids that we haven't already loaded during this danielebarchiesi@4: // page request. danielebarchiesi@4: $ids_to_check = array_diff($ids, array_keys($checked_ids)); danielebarchiesi@4: if (!empty($ids_to_check)) { danielebarchiesi@4: $query = db_select('node', 'n') danielebarchiesi@4: ->addTag('node_access') danielebarchiesi@4: ->addMetaData('id', 'node_reference_field_prepare_view') danielebarchiesi@4: ->addMetaData('field', $field) danielebarchiesi@4: ->fields('n', array('nid')) danielebarchiesi@4: // WHERE n.nid IN (nids to check) AND ... danielebarchiesi@4: ->condition('n.nid', $ids_to_check, 'IN'); danielebarchiesi@4: danielebarchiesi@4: // Unless the user has the right permissions, restrict on the node status. danielebarchiesi@4: // (note: the 'view any unpublished content' permission is provided by the danielebarchiesi@4: // 'view_unpublished' contrib module.) danielebarchiesi@4: if (!user_access('bypass node access') && !user_access('view any unpublished content')) { danielebarchiesi@4: // ... AND n.status = 1 danielebarchiesi@4: $status_condition = db_or() danielebarchiesi@4: ->condition('n.status', NODE_PUBLISHED); danielebarchiesi@4: danielebarchiesi@4: // Take the 'view own unpublished content' permission into account to danielebarchiesi@4: // decide whether some unpublished nodes should still be visible. danielebarchiesi@4: if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => NODE_NOT_PUBLISHED))->fetchCol()) { danielebarchiesi@4: // ... AND (n.status = 1 OR n.nid IN (own unpublished)) danielebarchiesi@4: $status_condition danielebarchiesi@4: ->condition('n.nid', $own_unpublished, 'IN'); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $query->condition($status_condition); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $accessible_ids = $query->execute()->fetchAllAssoc('nid'); danielebarchiesi@4: danielebarchiesi@4: // Populate our static list so that we do not query on those ids again. danielebarchiesi@4: foreach ($ids_to_check as $id) { danielebarchiesi@4: $checked_ids[$id] = isset($accessible_ids[$id]); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: foreach ($items as $id => $entity_items) { danielebarchiesi@4: foreach ($entity_items as $delta => $item) { danielebarchiesi@4: if (is_array($item) && !empty($item['nid']) && !empty($checked_ids[$item['nid']])) { danielebarchiesi@4: $items[$id][$delta]['access'] = TRUE; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_is_empty(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_is_empty($item, $field) { danielebarchiesi@4: // nid = 0 is empty too, which is exactly what we want. danielebarchiesi@4: return empty($item['nid']); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_formatter_info(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_formatter_info() { danielebarchiesi@4: $ret = array( danielebarchiesi@4: 'node_reference_default' => array( danielebarchiesi@4: 'label' => t('Title (link)'), danielebarchiesi@4: 'description' => t('Display the title of the referenced node as a link to the node page.'), danielebarchiesi@4: 'field types' => array('node_reference'), danielebarchiesi@4: ), danielebarchiesi@4: 'node_reference_plain' => array( danielebarchiesi@4: 'label' => t('Title (no link)'), danielebarchiesi@4: 'description' => t('Display the title of the referenced node as plain text.'), danielebarchiesi@4: 'field types' => array('node_reference'), danielebarchiesi@4: ), danielebarchiesi@4: 'node_reference_node' => array( danielebarchiesi@4: 'label' => t('Rendered node'), danielebarchiesi@4: 'description' => t('Display the referenced node in a specific view mode'), danielebarchiesi@4: 'field types' => array('node_reference'), danielebarchiesi@4: 'settings' => array('node_reference_view_mode' => 'full'), danielebarchiesi@4: ), danielebarchiesi@4: 'node_reference_nid' => array( danielebarchiesi@4: 'label' => t('Node ID'), danielebarchiesi@4: 'description' => t('Display the referenced node ID'), danielebarchiesi@4: 'field types' => array('node_reference'), danielebarchiesi@4: ), danielebarchiesi@4: 'node_reference_path' => array( danielebarchiesi@4: 'label' => t('URL as plain text'), danielebarchiesi@4: 'description' => t('Display the URL of the referenced node'), danielebarchiesi@4: 'field types' => array('node_reference'), danielebarchiesi@4: 'settings' => array( danielebarchiesi@4: 'alias' => TRUE, danielebarchiesi@4: 'absolute' => FALSE danielebarchiesi@4: ), danielebarchiesi@4: ), danielebarchiesi@4: ); danielebarchiesi@4: return $ret; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_formatter_settings_form(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { danielebarchiesi@4: $display = $instance['display'][$view_mode]; danielebarchiesi@4: $settings = $display['settings']; danielebarchiesi@4: danielebarchiesi@4: $element = array(); danielebarchiesi@4: danielebarchiesi@4: switch ($display['type']) { danielebarchiesi@4: case 'node_reference_node': danielebarchiesi@4: $entity_info = entity_get_info('node'); danielebarchiesi@4: $modes = $entity_info['view modes']; danielebarchiesi@4: $options = array(); danielebarchiesi@4: foreach ($modes as $name => $mode) { danielebarchiesi@4: $options[$name] = $mode['label']; danielebarchiesi@4: } danielebarchiesi@4: $element['node_reference_view_mode'] = array( danielebarchiesi@4: '#title' => t('View mode'), danielebarchiesi@4: '#type' => 'select', danielebarchiesi@4: '#options' => $options, danielebarchiesi@4: '#default_value' => $settings['node_reference_view_mode'], danielebarchiesi@4: // Never empty, so no #empty_option danielebarchiesi@4: ); danielebarchiesi@4: break; danielebarchiesi@4: danielebarchiesi@4: case 'node_reference_path': danielebarchiesi@4: $element['alias'] = array( danielebarchiesi@4: '#type' => 'checkbox', danielebarchiesi@4: '#title' => t('Display the aliased path (if exists) instead of the system path'), danielebarchiesi@4: '#default_value' => $settings['alias'], danielebarchiesi@4: ); danielebarchiesi@4: $element['absolute'] = array( danielebarchiesi@4: '#type' => 'checkbox', danielebarchiesi@4: '#title' => t('Display an absolute URL'), danielebarchiesi@4: '#default_value' => $settings['absolute'], danielebarchiesi@4: ); danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return $element; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_formatter_settings_summary(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_formatter_settings_summary($field, $instance, $view_mode) { danielebarchiesi@4: $display = $instance['display'][$view_mode]; danielebarchiesi@4: $settings = $display['settings']; danielebarchiesi@4: $summary = array(); danielebarchiesi@4: danielebarchiesi@4: switch ($display['type']) { danielebarchiesi@4: case 'node_reference_node': danielebarchiesi@4: $entity_info = entity_get_info('node'); danielebarchiesi@4: $modes = $entity_info['view modes']; danielebarchiesi@4: $mode = $modes[$settings['node_reference_view_mode']]['label']; danielebarchiesi@4: $summary[] = t('View mode: %mode', array('%mode' => $mode)); danielebarchiesi@4: break; danielebarchiesi@4: danielebarchiesi@4: case 'node_reference_path': danielebarchiesi@4: $summary[] = t('Aliased path: %yes_no', array('%yes_no' => $settings['alias'] ? t('Yes') : t('No'))); danielebarchiesi@4: $summary[] = t('Absolute URL: %yes_no', array('%yes_no' => $settings['absolute'] ? t('Yes') : t('No'))); danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return implode('
', $summary); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_formatter_prepare_view(). danielebarchiesi@4: * danielebarchiesi@4: * Preload all nodes referenced by items using 'full entity' formatters. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { danielebarchiesi@4: // Load the referenced nodes, except for the 'node_reference_nid' which does danielebarchiesi@4: // not need full objects. danielebarchiesi@4: danielebarchiesi@4: // Collect ids to load. danielebarchiesi@4: $ids = array(); danielebarchiesi@4: foreach ($displays as $id => $display) { danielebarchiesi@4: if ($display['type'] != 'node_reference_nid') { danielebarchiesi@4: foreach ($items[$id] as $delta => $item) { danielebarchiesi@4: if ($item['access']) { danielebarchiesi@4: $ids[$item['nid']] = $item['nid']; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: $entities = node_load_multiple($ids); danielebarchiesi@4: danielebarchiesi@4: // Add the loaded nodes to the items. danielebarchiesi@4: foreach ($displays as $id => $display) { danielebarchiesi@4: if ($display['type'] != 'node_reference_nid') { danielebarchiesi@4: foreach ($items[$id] as $delta => $item) { danielebarchiesi@4: if ($item['access']) { danielebarchiesi@4: $items[$id][$delta]['node'] = $entities[$item['nid']]; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_formatter_view(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { danielebarchiesi@4: $settings = $display['settings']; danielebarchiesi@4: $result = array(); danielebarchiesi@4: danielebarchiesi@4: switch ($display['type']) { danielebarchiesi@4: case 'node_reference_default': danielebarchiesi@4: case 'node_reference_plain': danielebarchiesi@4: foreach ($items as $delta => $item) { danielebarchiesi@4: if ($item['access']) { danielebarchiesi@4: $node = $item['node']; danielebarchiesi@4: $label = entity_label('node', $node); danielebarchiesi@4: if ($display['type'] == 'node_reference_default') { danielebarchiesi@4: $uri = entity_uri('node', $node); danielebarchiesi@4: $result[$delta] = array( danielebarchiesi@4: '#type' => 'link', danielebarchiesi@4: '#title' => $label, danielebarchiesi@4: '#href' => $uri['path'], danielebarchiesi@4: '#options' => $uri['options'], danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: $result[$delta] = array( danielebarchiesi@4: '#markup' => check_plain($label), danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: if (!$node->status) { danielebarchiesi@4: $result[$delta]['#prefix'] = ''; danielebarchiesi@4: $result[$delta]['#suffix'] = ''; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: break; danielebarchiesi@4: danielebarchiesi@4: case 'node_reference_node': danielebarchiesi@4: // To prevent infinite recursion caused by reference cycles, we store danielebarchiesi@4: // diplayed nodes in a recursion queue. danielebarchiesi@4: $recursion_queue = &drupal_static(__FUNCTION__, array()); danielebarchiesi@4: danielebarchiesi@4: // If no 'referencing entity' is set, we are starting a new 'reference danielebarchiesi@4: // thread' and need to reset the queue. danielebarchiesi@4: // @todo Bug: $entity->referencing_entity on nodes referenced in a different danielebarchiesi@4: // thread on the page. E.g: 1 references 1+2 / 2 references 1+2 / visit homepage. danielebarchiesi@4: // We'd need a more accurate way... danielebarchiesi@4: if (!isset($entity->referencing_entity)) { danielebarchiesi@4: $recursion_queue = array(); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // The recursion queue only needs to track nodes. danielebarchiesi@4: if ($entity_type == 'node') { danielebarchiesi@4: list($id) = entity_extract_ids($entity_type, $entity); danielebarchiesi@4: $recursion_queue[$id] = $id; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Check the recursion queue to determine which nodes should be fully danielebarchiesi@4: // displayed, and which nodes will only be displayed as a title. danielebarchiesi@4: $nodes_display = array(); danielebarchiesi@4: foreach ($items as $delta => $item) { danielebarchiesi@4: if ($item['access'] && !isset($recursion_queue[$item['nid']])) { danielebarchiesi@4: $nodes_display[$item['nid']] = $item['node']; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Load and build the fully displayed nodes. danielebarchiesi@4: if ($nodes_display) { danielebarchiesi@4: foreach ($nodes_display as $nid => $node) { danielebarchiesi@4: $nodes_display[$nid]->referencing_entity = $entity; danielebarchiesi@4: $nodes_display[$nid]->referencing_field = $field['field_name']; danielebarchiesi@4: } danielebarchiesi@4: $nodes_built = node_view_multiple($nodes_display, $settings['node_reference_view_mode']); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Assemble the render array. danielebarchiesi@4: foreach ($items as $delta => $item) { danielebarchiesi@4: if ($item['access']) { danielebarchiesi@4: if (isset($nodes_display[$item['nid']])) { danielebarchiesi@4: $result[$delta] = $nodes_built['nodes'][$item['nid']]; danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: $node = $item['node']; danielebarchiesi@4: $label = entity_label('node', $node); danielebarchiesi@4: $uri = entity_uri('node', $node); danielebarchiesi@4: $result[$delta] = array( danielebarchiesi@4: '#type' => 'link', danielebarchiesi@4: '#title' => $label, danielebarchiesi@4: '#href' => $uri['path'], danielebarchiesi@4: '#options' => $uri['options'], danielebarchiesi@4: ); danielebarchiesi@4: if (!$node->status) { danielebarchiesi@4: $result[$delta]['#prefix'] = ''; danielebarchiesi@4: $result[$delta]['#suffix'] = ''; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: break; danielebarchiesi@4: danielebarchiesi@4: case 'node_reference_nid': danielebarchiesi@4: foreach ($items as $delta => $item) { danielebarchiesi@4: if ($item['access']) { danielebarchiesi@4: $result[$delta] = array( danielebarchiesi@4: '#markup' => $item['nid'], danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: break; danielebarchiesi@4: danielebarchiesi@4: case 'node_reference_path': danielebarchiesi@4: foreach ($items as $delta => $item) { danielebarchiesi@4: if ($item['access']) { danielebarchiesi@4: $uri = entity_uri('node', $item['node']); danielebarchiesi@4: $options = array( danielebarchiesi@4: 'absolute' => $settings['absolute'], danielebarchiesi@4: 'alias' => !$settings['alias'], danielebarchiesi@4: ); danielebarchiesi@4: danielebarchiesi@4: $options += $uri['options']; danielebarchiesi@4: $result[$delta] = array( danielebarchiesi@4: '#markup' => url($uri['path'], $options), danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return $result; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_widget_info(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_widget_info() { danielebarchiesi@4: return array( danielebarchiesi@4: 'node_reference_autocomplete' => array( danielebarchiesi@4: 'label' => t('Autocomplete text field'), danielebarchiesi@4: 'description' => t('Display the list of referenceable nodes as a textfield with autocomplete behaviour.'), danielebarchiesi@4: 'field types' => array('node_reference'), danielebarchiesi@4: 'settings' => array( danielebarchiesi@4: 'autocomplete_match' => 'contains', danielebarchiesi@4: 'size' => 60, danielebarchiesi@4: 'autocomplete_path' => 'node_reference/autocomplete', danielebarchiesi@4: ), danielebarchiesi@4: ), danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_widget_info_alter(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_widget_info_alter(&$info) { danielebarchiesi@4: $info['options_select']['field types'][] = 'node_reference'; danielebarchiesi@4: $info['options_buttons']['field types'][] = 'node_reference'; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_widget_settings_form(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_widget_settings_form($field, $instance) { danielebarchiesi@4: $widget = $instance['widget']; danielebarchiesi@4: $defaults = field_info_widget_settings($widget['type']); danielebarchiesi@4: $settings = array_merge($defaults, $widget['settings']); danielebarchiesi@4: danielebarchiesi@4: $form = array(); danielebarchiesi@4: if ($widget['type'] == 'node_reference_autocomplete') { danielebarchiesi@4: $form['autocomplete_match'] = array( danielebarchiesi@4: '#type' => 'select', danielebarchiesi@4: '#title' => t('Autocomplete matching'), danielebarchiesi@4: '#default_value' => $settings['autocomplete_match'], danielebarchiesi@4: '#options' => array( danielebarchiesi@4: 'starts_with' => t('Starts with'), danielebarchiesi@4: 'contains' => t('Contains'), danielebarchiesi@4: ), danielebarchiesi@4: '#description' => t('Select the method used to collect autocomplete suggestions. Note that Contains can cause performance issues on sites with thousands of nodes.'), danielebarchiesi@4: ); danielebarchiesi@4: $form['size'] = array( danielebarchiesi@4: '#type' => 'textfield', danielebarchiesi@4: '#title' => t('Size of textfield'), danielebarchiesi@4: '#default_value' => $settings['size'], danielebarchiesi@4: '#element_validate' => array('_element_validate_integer_positive'), danielebarchiesi@4: '#required' => TRUE, danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: return $form; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_widget_form(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { danielebarchiesi@4: switch ($instance['widget']['type']) { danielebarchiesi@4: case 'node_reference_autocomplete': danielebarchiesi@4: $element += array( danielebarchiesi@4: '#type' => 'textfield', danielebarchiesi@4: '#default_value' => isset($items[$delta]['nid']) ? $items[$delta]['nid'] : NULL, danielebarchiesi@4: '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/' . $field['field_name'], danielebarchiesi@4: '#size' => $instance['widget']['settings']['size'], danielebarchiesi@4: '#maxlength' => NULL, danielebarchiesi@4: '#element_validate' => array('node_reference_autocomplete_validate'), danielebarchiesi@4: '#value_callback' => 'node_reference_autocomplete_value', danielebarchiesi@4: ); danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return array('nid' => $element); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Value callback for a node_reference autocomplete element. danielebarchiesi@4: * danielebarchiesi@4: * Replace the node nid with a node title. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_autocomplete_value($element, $input = FALSE, $form_state) { danielebarchiesi@4: if ($input === FALSE) { danielebarchiesi@4: // We're building the displayed 'default value': expand the raw nid into danielebarchiesi@4: // "node title [nid:n]". danielebarchiesi@4: $nid = $element['#default_value']; danielebarchiesi@4: if (!empty($nid)) { danielebarchiesi@4: $q = db_select('node', 'n'); danielebarchiesi@4: $node_title_alias = $q->addField('n', 'title'); danielebarchiesi@4: $q->addTag('node_access') danielebarchiesi@4: ->condition('n.nid', $nid) danielebarchiesi@4: ->range(0, 1); danielebarchiesi@4: $result = $q->execute(); danielebarchiesi@4: // @todo If no result (node doesn't exist or no access). danielebarchiesi@4: $value = $result->fetchField(); danielebarchiesi@4: $value .= ' [nid:' . $nid . ']'; danielebarchiesi@4: return $value; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Validation callback for a node_reference autocomplete element. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_autocomplete_validate($element, &$form_state, $form) { danielebarchiesi@4: $field = field_widget_field($element, $form_state); danielebarchiesi@4: $instance = field_widget_instance($element, $form_state); danielebarchiesi@4: danielebarchiesi@4: $value = $element['#value']; danielebarchiesi@4: $nid = NULL; danielebarchiesi@4: danielebarchiesi@4: if (!empty($value)) { danielebarchiesi@4: // Check whether we have an explicit "[nid:n]" input. danielebarchiesi@4: preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $value, $matches); danielebarchiesi@4: if (!empty($matches)) { danielebarchiesi@4: // Explicit nid. Check that the 'title' part matches the actual title for danielebarchiesi@4: // the nid. danielebarchiesi@4: list(, $title, $nid) = $matches; danielebarchiesi@4: if (!empty($title)) { danielebarchiesi@4: $real_title = db_select('node', 'n') danielebarchiesi@4: ->fields('n', array('title')) danielebarchiesi@4: ->condition('n.nid', $nid) danielebarchiesi@4: ->execute() danielebarchiesi@4: ->fetchField(); danielebarchiesi@4: if (trim($title) != trim($real_title)) { danielebarchiesi@4: form_error($element, t('%name: title mismatch. Please check your selection.', array('%name' => $instance['label']))); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: // No explicit nid (the submitted value was not populated by autocomplete danielebarchiesi@4: // selection). Get the nid of a referencable node from the entered title. danielebarchiesi@4: $options = array( danielebarchiesi@4: 'string' => $value, danielebarchiesi@4: 'match' => 'equals', danielebarchiesi@4: 'limit' => 1, danielebarchiesi@4: ); danielebarchiesi@4: $references = node_reference_potential_references($field, $options); danielebarchiesi@4: if ($references) { danielebarchiesi@4: // @todo The best thing would be to present the user with an danielebarchiesi@4: // additional form, allowing the user to choose between valid danielebarchiesi@4: // candidates with the same title. ATM, we pick the first danielebarchiesi@4: // matching candidate... danielebarchiesi@4: $nid = key($references); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: form_error($element, t('%name: found no valid post with that title.', array('%name' => $instance['label']))); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Set the element's value as the node id that was extracted from the entered danielebarchiesi@4: // input. danielebarchiesi@4: form_set_value($element, $nid, $form_state); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_widget_error(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_widget_error($element, $error, $form, &$form_state) { danielebarchiesi@4: form_error($element['nid'], $error['message']); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Builds a list of referenceable nodes suitable for the '#option' FAPI property. danielebarchiesi@4: * danielebarchiesi@4: * Warning: the function does NOT take care of encoding or escaping the node danielebarchiesi@4: * titles. Proper massaging needs to be performed by the caller, according to danielebarchiesi@4: * the destination FAPI '#type' (radios / checkboxes / select). danielebarchiesi@4: * danielebarchiesi@4: * @param $field danielebarchiesi@4: * The field definition. danielebarchiesi@4: * @param $flat danielebarchiesi@4: * Whether optgroups are allowed. danielebarchiesi@4: * danielebarchiesi@4: * @return danielebarchiesi@4: * An array of referenceable node titles, keyed by node id. If the $flat danielebarchiesi@4: * parameter is TRUE, the list might be nested by optgroup first. danielebarchiesi@4: */ danielebarchiesi@4: function _node_reference_options($field, $flat = TRUE) { danielebarchiesi@4: $references = node_reference_potential_references($field); danielebarchiesi@4: danielebarchiesi@4: $options = array(); danielebarchiesi@4: foreach ($references as $key => $value) { danielebarchiesi@4: // The label, displayed in selects and checkboxes/radios, should have HTML danielebarchiesi@4: // entities unencoded. The widgets (core's options.module) take care of danielebarchiesi@4: // applying the relevant filters (strip_tags() or filter_xss()). danielebarchiesi@4: $label = html_entity_decode($value['rendered'], ENT_QUOTES); danielebarchiesi@4: if (empty($value['group']) || $flat) { danielebarchiesi@4: $options[$key] = $label; danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: // The group name, displayed in selects, cannot contain tags, and should danielebarchiesi@4: // have HTML entities unencoded. danielebarchiesi@4: $group = html_entity_decode(strip_tags($value['group']), ENT_QUOTES); danielebarchiesi@4: $options[$group][$key] = $label; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return $options; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Retrieves an array of candidate referenceable nodes. danielebarchiesi@4: * danielebarchiesi@4: * This info is used in various places (allowed values, autocomplete danielebarchiesi@4: * results, input validation...). Some of them only need the nids, danielebarchiesi@4: * others nid + titles, others yet nid + titles + rendered row (for danielebarchiesi@4: * display in widgets). danielebarchiesi@4: * danielebarchiesi@4: * The array we return contains all the potentially needed information, danielebarchiesi@4: * and lets consumers use the parts they actually need. danielebarchiesi@4: * danielebarchiesi@4: * @param $field danielebarchiesi@4: * The field definition. danielebarchiesi@4: * @param $options danielebarchiesi@4: * An array of options to limit the scope of the returned list. The following danielebarchiesi@4: * key/value pairs are accepted: danielebarchiesi@4: * - string: string to filter titles on (used by autocomplete). danielebarchiesi@4: * - match: operator to match the above string against, can be any of: danielebarchiesi@4: * 'contains', 'equals', 'starts_with'. Defaults to 'contains'. danielebarchiesi@4: * - ids: array of specific node ids to lookup. danielebarchiesi@4: * - limit: maximum size of the the result set. Defaults to 0 (no limit). danielebarchiesi@4: * danielebarchiesi@4: * @return danielebarchiesi@4: * An array of valid nodes in the form: danielebarchiesi@4: * array( danielebarchiesi@4: * nid => array( danielebarchiesi@4: * 'title' => The node title, danielebarchiesi@4: * 'rendered' => The text to display in widgets (can be HTML) danielebarchiesi@4: * ), danielebarchiesi@4: * ... danielebarchiesi@4: * ) danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_potential_references($field, $options = array()) { danielebarchiesi@4: // Fill in default options. danielebarchiesi@4: $options += array( danielebarchiesi@4: 'string' => '', danielebarchiesi@4: 'match' => 'contains', danielebarchiesi@4: 'ids' => array(), danielebarchiesi@4: 'limit' => 0, danielebarchiesi@4: ); danielebarchiesi@4: danielebarchiesi@4: $results = &drupal_static(__FUNCTION__, array()); danielebarchiesi@4: danielebarchiesi@4: // Create unique id for static cache. danielebarchiesi@4: $cid = $field['field_name'] . ':' . $options['match'] . ':' danielebarchiesi@4: . ($options['string'] !== '' ? $options['string'] : implode('-', $options['ids'])) danielebarchiesi@4: . ':' . $options['limit']; danielebarchiesi@4: if (!isset($results[$cid])) { danielebarchiesi@4: $references = FALSE; danielebarchiesi@4: if (module_exists('views') && !empty($field['settings']['view']['view_name'])) { danielebarchiesi@4: $references = _node_reference_potential_references_views($field, $options); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: if ($references === FALSE) { danielebarchiesi@4: $references = _node_reference_potential_references_standard($field, $options); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Store the results. danielebarchiesi@4: $results[$cid] = !empty($references) ? $references : array(); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return $results[$cid]; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Helper function for node_reference_potential_references(). danielebarchiesi@4: * danielebarchiesi@4: * Case of Views-defined referenceable nodes. danielebarchiesi@4: */ danielebarchiesi@4: function _node_reference_potential_references_views($field, $options) { danielebarchiesi@4: $settings = $field['settings']['view']; danielebarchiesi@4: $options['title_field'] = 'title'; danielebarchiesi@4: return references_potential_references_view('node', $settings['view_name'], $settings['display_name'], $settings['args'], $options); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Helper function for node_reference_potential_references(). danielebarchiesi@4: * danielebarchiesi@4: * List of referenceable nodes defined by content types. danielebarchiesi@4: */ danielebarchiesi@4: function _node_reference_potential_references_standard($field, $options) { danielebarchiesi@4: // Avoid useless work danielebarchiesi@4: if (!count($field['settings']['referenceable_types'])) { danielebarchiesi@4: return array(); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $query = db_select('node', 'n'); danielebarchiesi@4: $node_nid_alias = $query->addField('n', 'nid'); danielebarchiesi@4: $node_title_alias = $query->addField('n', 'title', 'node_title'); danielebarchiesi@4: $node_type_alias = $query->addField('n', 'type', 'node_type'); danielebarchiesi@4: $query->addTag('node_access') danielebarchiesi@4: ->addMetaData('id', ' _node_reference_potential_references_standard') danielebarchiesi@4: ->addMetaData('field', $field) danielebarchiesi@4: ->addMetaData('options', $options); danielebarchiesi@4: danielebarchiesi@4: if (is_array($field['settings']['referenceable_types'])) { danielebarchiesi@4: $query->condition('n.type', $field['settings']['referenceable_types'], 'IN'); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: if ($options['string'] !== '') { danielebarchiesi@4: switch ($options['match']) { danielebarchiesi@4: case 'contains': danielebarchiesi@4: $query->condition('n.title', '%' . $options['string'] . '%', 'LIKE'); danielebarchiesi@4: break; danielebarchiesi@4: danielebarchiesi@4: case 'starts_with': danielebarchiesi@4: $query->condition('n.title', $options['string'] . '%', 'LIKE'); danielebarchiesi@4: break; danielebarchiesi@4: danielebarchiesi@4: case 'equals': danielebarchiesi@4: default: // no match type or incorrect match type: use "=" danielebarchiesi@4: $query->condition('n.title', $options['string']); danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: if ($options['ids']) { danielebarchiesi@4: $query->condition('n.nid', $options['ids'], 'IN'); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: if ($options['limit']) { danielebarchiesi@4: $query->range(0, $options['limit']); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: $query danielebarchiesi@4: ->orderBy($node_title_alias) danielebarchiesi@4: ->orderBy($node_type_alias); danielebarchiesi@4: danielebarchiesi@4: $result = $query->execute()->fetchAll(); danielebarchiesi@4: $references = array(); danielebarchiesi@4: foreach ($result as $node) { danielebarchiesi@4: $references[$node->nid] = array( danielebarchiesi@4: 'title' => $node->node_title, danielebarchiesi@4: 'rendered' => check_plain($node->node_title), danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: return $references; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Menu callback for the autocomplete results. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_autocomplete($entity_type, $bundle, $field_name, $string = '') { danielebarchiesi@4: $field = field_info_field($field_name); danielebarchiesi@4: $instance = field_info_instance($entity_type, $field_name, $bundle); danielebarchiesi@4: danielebarchiesi@4: $options = array( danielebarchiesi@4: 'string' => $string, danielebarchiesi@4: 'match' => $instance['widget']['settings']['autocomplete_match'], danielebarchiesi@4: 'limit' => 10, danielebarchiesi@4: ); danielebarchiesi@4: $references = node_reference_potential_references($field, $options); danielebarchiesi@4: danielebarchiesi@4: $matches = array(); danielebarchiesi@4: foreach ($references as $id => $row) { danielebarchiesi@4: // Markup is fine in autocompletion results (might happen when rendered danielebarchiesi@4: // through Views) but we want to remove hyperlinks. danielebarchiesi@4: $suggestion = preg_replace('/([^<]*)<\/a>/', '$2', $row['rendered']); danielebarchiesi@4: // Add a class wrapper for a few required CSS overrides. danielebarchiesi@4: $matches[$row['title'] . " [nid:$id]"] = '
' . $suggestion . '
'; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: drupal_json_output($matches); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_node_type_update(). danielebarchiesi@4: * danielebarchiesi@4: * Reflect type name changes to the 'referenceable types' settings: when danielebarchiesi@4: * the name of a type changes, the change needs to be reflected in the danielebarchiesi@4: * "referenceable types" setting for any node_reference field danielebarchiesi@4: * referencing it. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_node_type_update($info) { danielebarchiesi@4: if (!empty($info->old_type) && $info->old_type != $info->type) { danielebarchiesi@4: $fields = field_info_fields(); danielebarchiesi@4: foreach ($fields as $field_name => $field) { danielebarchiesi@4: if ($field['type'] == 'node_reference' && isset($field['settings']['referenceable_types'][$info->old_type])) { danielebarchiesi@4: $field['settings']['referenceable_types'][$info->type] = empty($field['settings']['referenceable_types'][$info->old_type]) ? 0 : $info->type; danielebarchiesi@4: unset($field['settings']['referenceable_types'][$info->old_type]); danielebarchiesi@4: field_update_field($field); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Theme preprocess function. danielebarchiesi@4: * danielebarchiesi@4: * Allows specific node templates for nodes displayed as values of a danielebarchiesi@4: * node_reference field with a specific view mode. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_preprocess_node(&$vars) { danielebarchiesi@4: // The 'referencing_field' attribute of the node is added by the danielebarchiesi@4: // node_reference_node mode formatter (display referenced node danielebarchiesi@4: // in a specific view mode). danielebarchiesi@4: if (!empty($vars['node']->referencing_field)) { danielebarchiesi@4: $node = $vars['node']; danielebarchiesi@4: $field_name = $node->referencing_field; danielebarchiesi@4: $vars['theme_hook_suggestions'][] = 'node__node_reference'; danielebarchiesi@4: $vars['theme_hook_suggestions'][] = 'node__node_reference__' . $field_name; danielebarchiesi@4: $vars['theme_hook_suggestions'][] = 'node__node_reference__' . $node->type; danielebarchiesi@4: $vars['theme_hook_suggestions'][] = 'node__node_reference__' . $field_name . '__' . $node->type; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_prepare_translation(). danielebarchiesi@4: * danielebarchiesi@4: * When preparing a translation, load any translations of existing danielebarchiesi@4: * references. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items, $source_entity, $source_langcode) { danielebarchiesi@4: if (isset($items) && is_array($items)) { danielebarchiesi@4: // Match each reference with its matching translation, if it exists. danielebarchiesi@4: foreach ($items as $key => $item) { danielebarchiesi@4: $reference_node = node_load($item['nid']); danielebarchiesi@4: $items[$key]['nid'] = node_reference_find_translation($reference_node, $entity->language); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Find a translation for a specific node reference, if it exists. danielebarchiesi@4: * danielebarchiesi@4: * @param $reference_node danielebarchiesi@4: * The untranslated node reference. danielebarchiesi@4: * @param $langcode danielebarchiesi@4: * danielebarchiesi@4: * @return danielebarchiesi@4: * A nid for the translation of the node reference, danielebarchiesi@4: * otherwise the original untranslated nid if no translation exists. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_find_translation($reference_node, $langcode) { danielebarchiesi@4: // Check if the source node translation is set and if translations are supported. danielebarchiesi@4: if (isset($reference_node->tnid) && translation_supported_type($reference_node->type)) { danielebarchiesi@4: // Determine whether an alternative language is being used. danielebarchiesi@4: if (!empty($reference_node->language) && $reference_node->language != $langcode) { danielebarchiesi@4: // Return a corresponding translation nid for the reference (if it exists). danielebarchiesi@4: $translations = translation_node_get_translations($reference_node->tnid); danielebarchiesi@4: if (isset($translations[$langcode])) { danielebarchiesi@4: return $translations[$langcode]->nid; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: // Return the untranslated reference nid, no matching translations found. danielebarchiesi@4: return $reference_node->nid; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_options_list(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_options_list($field) { danielebarchiesi@4: return _node_reference_options($field, FALSE); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_content_migrate_field_alter(). danielebarchiesi@4: * danielebarchiesi@4: * Use this to tweak the conversion of field settings from the D6 style to the danielebarchiesi@4: * D7 style for specific situations not handled by basic conversion, as when danielebarchiesi@4: * field types or settings are changed. danielebarchiesi@4: * danielebarchiesi@4: * $field_value['widget_type'] is available to danielebarchiesi@4: * see what widget type was originally used. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_content_migrate_field_alter(&$field_value, $instance_value) { danielebarchiesi@4: switch ($field_value['module']) { danielebarchiesi@4: case 'nodereference': danielebarchiesi@4: $field_value['module'] = 'node_reference'; danielebarchiesi@4: $field_value['type'] = 'node_reference'; danielebarchiesi@4: danielebarchiesi@4: // Translate 'view' settings. danielebarchiesi@4: $view_name = isset($field_value['settings']['advanced_view']) ? $field_value['settings']['advanced_view'] : ''; danielebarchiesi@4: $view_args = isset($field_value['settings']['advanced_view_args']) ? $field_value['settings']['advanced_view_args'] : ''; danielebarchiesi@4: $view_args = array_map('trim', explode(',', $view_args)); danielebarchiesi@4: $field_value['settings']['view'] = array( danielebarchiesi@4: 'view_name' => $view_name, danielebarchiesi@4: 'display_name' => 'default', danielebarchiesi@4: 'args' => $view_args, danielebarchiesi@4: ); danielebarchiesi@4: if ($view_name) { danielebarchiesi@4: $field_value['messages'][] = t("The field uses the view @view_name to determine referenceable nodes. You will need to manually edit the view and add a display of type 'References'.", array('@view_name' => $view_name)); danielebarchiesi@4: } danielebarchiesi@4: unset($field_value['settings']['advanced_view']); danielebarchiesi@4: unset($field_value['settings']['advanced_view_args']); danielebarchiesi@4: danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_content_migrate_instance_alter(). danielebarchiesi@4: * danielebarchiesi@4: * Use this to tweak the conversion of instance or widget settings from the D6 danielebarchiesi@4: * style to the D7 style for specific situations not handled by basic danielebarchiesi@4: * conversion, as when formatter or widget names or settings are changed. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_content_migrate_instance_alter(&$instance_value, $field_value) { danielebarchiesi@4: switch ($field_value['type']) { danielebarchiesi@4: case 'nodereference': danielebarchiesi@4: // Massage formatters. danielebarchiesi@4: foreach ($instance_value['display'] as $context => &$display) { danielebarchiesi@4: switch ($display['type']) { danielebarchiesi@4: case 'full': danielebarchiesi@4: case 'teaser': danielebarchiesi@4: // Those two formatters have been merged into danielebarchiesi@4: // 'node_reference_view_mode', with a formatter setting. danielebarchiesi@4: $display['type'] = 'node_reference_node'; danielebarchiesi@4: $display['settings']['node_reference_view_mode'] = $display['type']; danielebarchiesi@4: break; danielebarchiesi@4: danielebarchiesi@4: default: danielebarchiesi@4: // The formatter names changed, all are prefixed with danielebarchiesi@4: // 'node_reference_'. danielebarchiesi@4: $display['type'] = 'node_reference_' . $display['type']; danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: // Massage the widget. danielebarchiesi@4: switch ($instance_value['widget']['type']) { danielebarchiesi@4: case 'nodereference_autocomplete': danielebarchiesi@4: $instance_value['widget']['type'] = 'node_reference_autocomplete'; danielebarchiesi@4: $instance_value['widget']['module'] = 'node_reference'; danielebarchiesi@4: break; danielebarchiesi@4: case 'nodereference_select': danielebarchiesi@4: $instance_value['widget']['type'] = 'options_select'; danielebarchiesi@4: $instance_value['widget']['module'] = 'options'; danielebarchiesi@4: break; danielebarchiesi@4: case 'nodereference_buttons': danielebarchiesi@4: $instance_value['widget']['type'] = 'options_buttons'; danielebarchiesi@4: $instance_value['widget']['module'] = 'options'; danielebarchiesi@4: } danielebarchiesi@4: break; danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_views_data(). danielebarchiesi@4: * danielebarchiesi@4: * In addition to the default field information we add the relationship for danielebarchiesi@4: * views to connect back to the node table. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_views_data($field) { danielebarchiesi@4: // No module_load_include(): this hook is invoked from danielebarchiesi@4: // views/modules/field.views.inc, which is where that function is defined. danielebarchiesi@4: $data = field_views_field_default_views_data($field); danielebarchiesi@4: danielebarchiesi@4: $storage = $field['storage']['details']['sql']; danielebarchiesi@4: danielebarchiesi@4: foreach ($storage as $age => $table_data) { danielebarchiesi@4: $table = key($table_data); danielebarchiesi@4: $columns = current($table_data); danielebarchiesi@4: $id_column = $columns['nid']; danielebarchiesi@4: if (isset($data[$table])) { danielebarchiesi@4: // Filter: swap the handler to the 'in' operator. The callback receives danielebarchiesi@4: // the field name instead of the whole $field structure to keep views danielebarchiesi@4: // data to a reasonable size. danielebarchiesi@4: $data[$table][$id_column]['filter']['handler'] = 'views_handler_filter_in_operator'; danielebarchiesi@4: $data[$table][$id_column]['filter']['options callback'] = 'node_reference_views_filter_options'; danielebarchiesi@4: $data[$table][$id_column]['filter']['options arguments'] = array($field['field_name']); danielebarchiesi@4: danielebarchiesi@4: // Argument: display node.title in argument titles (handled in our custom danielebarchiesi@4: // handler) and summary lists (handled by the base views_handler_argument danielebarchiesi@4: // handler). danielebarchiesi@4: // Both mechanisms rely on the 'name table' and 'name field' information danielebarchiesi@4: // below, by joining to a separate copy of the base table from the field danielebarchiesi@4: // data table. danielebarchiesi@4: $data[$table][$id_column]['argument']['handler'] = 'references_handler_argument'; danielebarchiesi@4: $data[$table][$id_column]['argument']['name table'] = $table . '_reference'; danielebarchiesi@4: $data[$table][$id_column]['argument']['name field'] = 'title'; danielebarchiesi@4: $data[$table . '_reference']['table']['join'][$table] = array( danielebarchiesi@4: 'left_field' => $id_column, danielebarchiesi@4: 'table' => 'node', danielebarchiesi@4: 'field' => 'nid', danielebarchiesi@4: ); danielebarchiesi@4: danielebarchiesi@4: // Relationship. danielebarchiesi@4: $data[$table][$id_column]['relationship'] = array( danielebarchiesi@4: 'handler' => 'references_handler_relationship', danielebarchiesi@4: 'base' => 'node', danielebarchiesi@4: 'base field' => 'nid', danielebarchiesi@4: 'field' => $id_column, danielebarchiesi@4: 'label' => $field['field_name'], danielebarchiesi@4: 'field_name' => $field['field_name'], danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return $data; danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * Implements hook_field_views_data_views_data_alter(). danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_field_views_data_views_data_alter(&$data, $field) { danielebarchiesi@4: foreach ($field['bundles'] as $entity_type => $bundles) { danielebarchiesi@4: $entity_info = entity_get_info($entity_type); danielebarchiesi@4: $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type; danielebarchiesi@4: danielebarchiesi@4: list($label, $all_labels) = field_views_field_label($field['field_name']); danielebarchiesi@4: $entity = $entity_info['label']; danielebarchiesi@4: if ($entity == t('Node')) { danielebarchiesi@4: $entity = t('Content'); danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: // Only specify target entity type if the field is used in more than one. danielebarchiesi@4: if (count($field['bundles']) > 1) { danielebarchiesi@4: $title = t('@field (@field_name) - reverse (to @entity)', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name'])); danielebarchiesi@4: } danielebarchiesi@4: else { danielebarchiesi@4: $title = t('@field (@field_name) - reverse', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name'])); danielebarchiesi@4: } danielebarchiesi@4: $data['node'][$pseudo_field_name]['relationship'] = array( danielebarchiesi@4: 'title' => $title, danielebarchiesi@4: 'help' => t('Relate each @entity referencing the node through @field.', array('@entity' => $entity, '@field' => $label)), danielebarchiesi@4: 'handler' => 'views_handler_relationship_entity_reverse', danielebarchiesi@4: 'field_name' => $field['field_name'], danielebarchiesi@4: 'field table' => _field_sql_storage_tablename($field), danielebarchiesi@4: 'field field' => $field['field_name'] . '_nid', danielebarchiesi@4: 'base' => $entity_info['base table'], danielebarchiesi@4: 'base field' => $entity_info['entity keys']['id'], danielebarchiesi@4: 'label' => t('!field_name', array('!field_name' => $field['field_name'])), danielebarchiesi@4: ); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: /** danielebarchiesi@4: * 'options callback' for the views_handler_filter_in_operator filter. danielebarchiesi@4: * danielebarchiesi@4: * @param $field_name danielebarchiesi@4: * The field name. danielebarchiesi@4: */ danielebarchiesi@4: function node_reference_views_filter_options($field_name) { danielebarchiesi@4: $options = array(); danielebarchiesi@4: danielebarchiesi@4: if ($field = field_info_field($field_name)) { danielebarchiesi@4: $options = _node_reference_options($field, TRUE); danielebarchiesi@4: danielebarchiesi@4: // The options are displayed in checkboxes within the filter admin form, and danielebarchiesi@4: // in a select within an exposed filter. Checkboxes accept HTML, other danielebarchiesi@4: // entities should be encoded; selects require the exact opposite: no HTML, danielebarchiesi@4: // no encoding. We go for a middle ground: strip tags, leave entities danielebarchiesi@4: // unencoded. danielebarchiesi@4: foreach ($options as $key => $value) { danielebarchiesi@4: $options[$key] = strip_tags($value); danielebarchiesi@4: } danielebarchiesi@4: } danielebarchiesi@4: danielebarchiesi@4: return $options; danielebarchiesi@4: }