'nodereference/autocomplete', 'title' => t('node reference autocomplete'), 'callback' => 'nodereference_autocomplete', 'access' => user_access('access content'), 'type' => MENU_CALLBACK); } return $items; } /** * Implementation of hook_field_info(). */ function nodereference_field_info() { return array( 'nodereference' => array('label' => t('Node Reference')), ); } /** * Implementation of hook_field_settings(). */ function nodereference_field_settings($op, $field) { switch ($op) { case 'form': $form = array(); $form['referenceable_types'] = array( '#type' => 'checkboxes', '#title' => t('Content types that can be referenced'), '#multiple' => TRUE, '#default_value' => isset($field['referenceable_types']) ? $field['referenceable_types'] : array(), '#options' => array_map('check_plain', node_get_types('names')), ); if (module_exists('views')) { $views = array('--' => '--'); $result = db_query("SELECT name FROM {view_view} ORDER BY name"); while ($view = db_fetch_array($result)) { $views[t('Existing Views')][$view['name']] = $view['name']; } views_load_cache(); $default_views = _views_get_default_views(); foreach ($default_views as $view) { $views[t('Default Views')][$view->name] = $view->name; } if (count($views) > 1) { $form['advanced'] = array( '#type' => 'fieldset', '#title' => t('Advanced - Nodes that can be referenced (View)'), '#collapsible' => TRUE, '#collapsed' => !isset($field['advanced_view']) || $field['advanced_view'] == '--', ); $form['advanced']['advanced_view'] = array( '#type' => 'select', '#title' => t('View'), '#options' => $views, '#default_value' => isset($field['advanced_view']) ? $field['advanced_view'] : '--', '#description' => t('Choose the "Views module" view that selects the nodes that can be referenced.
Note :'), ); $form['advanced']['advanced_view_args'] = array( '#type' => 'textfield', '#title' => t('View arguments'), '#default_value' => isset($field['advanced_view_args']) ? $field['advanced_view_args'] : '', '#required' => FALSE, '#description' => t('Provide a comma separated list of arguments to pass to the view.'), ); } } return $form; case 'save': $settings = array('referenceable_types'); if (module_exists('views')) { $settings[] = 'advanced_view'; $settings[] = 'advanced_view_args'; } return $settings; case 'database columns': $columns = array( 'nid' => array('type' => 'int', 'not null' => TRUE, 'default' => '0'), ); return $columns; case 'filters': return array( 'default' => array( 'list' => '_nodereference_filter_handler', 'list-type' => 'list', 'operator' => 'views_handler_operator_or', 'value-type' => 'array', 'extra' => array('field' => $field), ), ); } } /** * Implementation of hook_field(). */ function nodereference_field($op, &$node, $field, &$items, $teaser, $page) { switch ($op) { case 'validate': $refs = _nodereference_potential_references($field, TRUE); foreach ($items as $delta => $item) { $error_field = isset($item['error_field']) ? $item['error_field'] : ''; unset($item['error_field']); if (!empty($item['nid'])) { if (!in_array($item['nid'], array_keys($refs))) { form_set_error($error_field, t('%name : This post can\'t be referenced.', array('%name' => t($field['widget']['label'])))); } } } return; } } /** * Implementation of hook_field_formatter_info(). */ function nodereference_field_formatter_info() { return array( 'default' => array( 'label' => t('Title (link)'), 'field types' => array('nodereference'), ), 'plain' => array( 'label' => t('Title (no link)'), 'field types' => array('nodereference'), ), 'full' => array( 'label' => t('Full node'), 'field types' => array('nodereference'), ), 'teaser' => array( 'label' => t('Teaser'), 'field types' => array('nodereference'), ), ); } /** * Implementation of hook_field_formatter(). */ function nodereference_field_formatter($field, $item, $formatter, $node) { static $titles = array(); // We store the rendered nids in order to prevent infinite recursion // when using the 'full node' / 'teaser' formatters. static $recursion_queue = array(); if (empty($item['nid']) || !is_numeric($item['nid'])) { return ''; } if ($formatter == 'full' || $formatter == 'teaser') { // 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($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. $formatter = 'default'; } elseif ($referenced_node = node_load($item['nid'])) { $referenced_node->referencing_node = $node; $referenced_node->referencing_field = $field; $titles[$item['nid']] = $referenced_node->title; } } if (!isset($titles[$item['nid']])) { $title = db_result(db_query("SELECT title FROM {node} WHERE nid=%d", $item['nid'])); $titles[$item['nid']] = $title ? $title : ''; } switch ($formatter) { case 'full': return $referenced_node ? node_view($referenced_node, FALSE) : ''; case 'teaser': return $referenced_node ? node_view($referenced_node, TRUE) : ''; case 'plain': return check_plain($titles[$item['nid']]); default: return $titles[$item['nid']] ? l($titles[$item['nid']], 'node/'. $item['nid']) : ''; } } /** * Implementation of hook_widget_info(). */ function nodereference_widget_info() { return array( 'nodereference_select' => array( 'label' => t('Select List'), 'field types' => array('nodereference'), ), 'nodereference_autocomplete' => array( 'label' => t('Autocomplete Text Field'), 'field types' => array('nodereference'), ), ); } /** * Implementation of hook_widget(). */ function nodereference_widget($op, &$node, $field, &$items) { if ($field['widget']['type'] == 'nodereference_select') { switch ($op) { case 'prepare form values': $items_transposed = content_transpose_array_rows_cols($items); $items['default nids'] = $items_transposed['nid']; break; case 'form': $form = array(); $options = _nodereference_potential_references($field, TRUE); foreach ($options as $key => $value) { $options[$key] = _nodereference_item($field, $value, FALSE); } if (!$field['required']) { $options = array(0 => t('')) + $options; } $form[$field['field_name']] = array('#tree' => TRUE); $form[$field['field_name']]['nids'] = array( '#type' => 'select', '#title' => t($field['widget']['label']), '#default_value' => $items['default nids'], '#multiple' => $field['multiple'], '#size' => $field['multiple'] ? min(count($options), 6) : 0, '#options' => $options, '#required' => $field['required'], '#description' => content_filter_xss(t($field['widget']['description'])), ); return $form; case 'process form values': if ($field['multiple']) { // if nothing selected, make it 'none' if (empty($items['nids'])) { $items['nids'] = array(0 => '0'); } // drop the 'none' options if other items were also selected elseif (count($items['nids']) > 1) { unset($items['nids'][0]); } $items = array_values(content_transpose_array_rows_cols(array('nid' => $items['nids']))); } else { $items[0]['nid'] = $items['nids']; } // Remove the widget's data representation so it isn't saved. unset($items['nids']); foreach ($items as $delta => $item) { $items[$delta]['error_field'] = $field['field_name'] .'][nids'; } } } else { switch ($op) { case 'prepare form values': foreach ($items as $delta => $item) { if (!empty($items[$delta]['nid'])) { $items[$delta]['default node_name'] = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $items[$delta]['nid'])); $items[$delta]['default node_name'] .= ' [nid:'. $items[$delta]['nid'] .']'; } } break; case 'form': $form = array(); $form[$field['field_name']] = array('#tree' => TRUE); if ($field['multiple']) { $form[$field['field_name']]['#type'] = 'fieldset'; $form[$field['field_name']]['#description'] = content_filter_xss(t($field['widget']['description'])); $delta = 0; foreach ($items as $item) { if ($item['nid']) { $form[$field['field_name']][$delta]['node_name'] = array( '#type' => 'textfield', '#title' => ($delta == 0) ? t($field['widget']['label']) : '', '#autocomplete_path' => 'nodereference/autocomplete/'. $field['field_name'], '#default_value' => $item['default node_name'], '#required' => ($delta == 0) ? $field['required'] : FALSE, ); $delta++; } } foreach (range($delta, $delta + 2) as $delta) { $form[$field['field_name']][$delta]['node_name'] = array( '#type' => 'textfield', '#title' => ($delta == 0) ? t($field['widget']['label']) : '', '#autocomplete_path' => 'nodereference/autocomplete/'. $field['field_name'], '#default_value' => '', '#required' => ($delta == 0) ? $field['required'] : FALSE, ); } } else { $form[$field['field_name']][0]['node_name'] = array( '#type' => 'textfield', '#title' => t($field['widget']['label']), '#autocomplete_path' => 'nodereference/autocomplete/'. $field['field_name'], '#default_value' => $items[0]['default node_name'], '#required' => $field['required'], '#description' => content_filter_xss( t( $field['widget']['description'] )), ); } return $form; case 'validate': foreach ($items as $delta => $item) { $error_field = $field['field_name'] .']['. $delta .'][node_name'; if (!empty($item['node_name'])) { preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $item['node_name'], $matches); if (!empty($matches)) { // explicit nid list(, $title, $nid) = $matches; if (!empty($title) && ($n = node_load($nid)) && $title != $n->title) { form_set_error($error_field, t('%name : Title mismatch. Please check your selection.', array('%name' => t($field['widget']['label'])))); } } else { // no explicit nid $nids = _nodereference_potential_references($field, FALSE, $item['node_name'], TRUE); if (empty($nids)) { form_set_error($error_field, t('%name: Found no valid post with that title.', array('%name' => t($field['widget']['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 = array_shift(array_keys($nids)); } } } } return; case 'process form values': foreach ($items as $delta => $item) { $nid = 0; if (!empty($item['node_name'])) { preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $item['node_name'], $matches); if (!empty($matches)) { // explicit nid $nid = $matches[2]; } else { // no explicit nid // 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... $nids = _nodereference_potential_references($field, FALSE, $item['node_name'], TRUE); $nid = (!empty($nids)) ? array_shift(array_keys($nids)) : 0; } } // Remove the widget's data representation so it isn't saved. unset($items[$delta]['node_name']); if (!empty($nid)) { $items[$delta]['nid'] = $nid; $items[$delta]['error_field'] = $field['field_name'] .']['. $delta .'][node_name'; } elseif ($delta > 0) { // Don't save empty fields when they're not the first value (keep '0' otherwise) unset($items[$delta]); } } break; } } } /** * Fetch an array of all candidate referenced nodes, for use in presenting the selection form to the user. */ function _nodereference_potential_references($field, $return_full_nodes = FALSE, $string = '', $exact_string = false) { if (module_exists('views') && isset($field['advanced_view']) && $field['advanced_view'] != '--' && ($view = views_get_view($field['advanced_view']))) { // advanced field : referenceable nodes defined by a view // let views.module build the query // arguments for the view $view_args = array(); if (isset($field['advanced_view_args'])) { // TODO: Support Tokens using token.module ? $view_args = array_map(trim, explode(',', $field['advanced_view_args'])); } if (isset($string)) { views_view_add_filter($view, 'node', 'title', $exact_string ? '=' : 'contains', $string, null); } // we do need title field, so add it if not present (unlikely, but...) $has_title = array_reduce($view->field, create_function('$a, $b', 'return ($b["field"] == "title") || $a;'), false); if (!$has_title) { views_view_add_field($view, 'node', 'title', ''); } views_load_cache(); views_sanitize_view($view); // make sure the fields get included in the query $view->page = true; $view->page_type = 'list'; // make sure the query is not cached unset($view->query); // Views 1.5- $view->is_cacheable = FALSE; // Views 1.6+ $view_result = views_build_view('result', $view, $view_args); $result = $view_result['result']; } else { // standard field : referenceable nodes defined by content types // build the appropriate query $related_types = array(); $args = array(); if (isset($field['referenceable_types'])) { foreach ($field['referenceable_types'] as $related_type) { if ($related_type) { $related_types[] = " n.type = '%s'"; $args[] = $related_type; } } } $related_clause = implode(' OR ', $related_types); if (!count($related_types)) { return array(); } if ($string !== '') { $string_clause = $exact_string ? " AND n.title = '%s'" : " AND n.title LIKE '%%%s%'"; $related_clause = "(". $related_clause .")". $string_clause; $args[] = $string; } $result = db_query(db_rewrite_sql("SELECT n.nid, n.title AS node_title, n.type AS node_type FROM {node} n WHERE ". $related_clause ." ORDER BY n.title, n.type"), $args); } if (db_num_rows($result) == 0) { return array(); } $rows = array(); while ($node = db_fetch_object($result)) { if ($return_full_nodes) { $rows[$node->nid] = $node; } else { $rows[$node->nid] = $node->node_title; } } return $rows; } /** * Retrieve a pipe delimited string of autocomplete suggestions */ function nodereference_autocomplete($field_name, $string = '') { $fields = content_fields(); $field = $fields[$field_name]; $matches = array(); foreach (_nodereference_potential_references($field, TRUE, $string) as $row) { $matches[$row->node_title .' [nid:'. $row->nid .']'] = _nodereference_item($field, $row); } print drupal_to_js($matches); exit(); } function _nodereference_item($field, $item, $html = TRUE) { if (module_exists('views') && isset($field['advanced_view']) && $field['advanced_view'] != '--' && ($view = views_get_view($field['advanced_view']))) { $output = theme('nodereference_item_advanced', $item, $view); if (!$html) { // Views theming runs check_plain (htmlentities) on the values. // We reverse that with html_entity_decode. $output = html_entity_decode(strip_tags($output), ENT_QUOTES); } } else { $output = theme('nodereference_item_simple', $item); $output = $html ? check_plain($output) : $output; } return $output; } function theme_nodereference_item_advanced($item, $view) { $fields = _views_get_fields(); $item_fields = array(); foreach ($view->field as $field) { $value = views_theme_field('views_handle_field', $field['queryname'], $fields, $field, $item, $view); // remove link tags (ex : for node titles) $value = preg_replace('/]*>(.*)<\/a>/iU', '$1', $value); if (!empty($value)) { $item_fields[] = "$value";; } } $output = implode(' - ', $item_fields); $output = "$output"; return $output; } function theme_nodereference_item_simple($item) { return $item->node_title; } /** * Provide a list of nodes to filter on. */ function _nodereference_filter_handler($op, $filterinfo) { $options = array(0 => t('')); $options = $options + _nodereference_potential_references($filterinfo['extra']['field']); return $options; } /** * Implementation of hook_panels_relationships(). */ function nodereference_panels_relationships() { $args = array(); $args['node_from_noderef'] = array( 'title' => t('Node from reference'), 'keyword' => 'nodereference', 'description' => t('Adds a node from a node reference in a node context; if multiple nodes are referenced, this will get the first referenced node only.'), 'required context' => new panels_required_context(t('Node'), 'node'), 'context' => 'nodereference_node_from_noderef_context', 'settings form' => 'nodereference_node_from_noderef_settings_form', 'settings form validate' => 'nodereference_node_from_noderef_settings_form_validate', ); return $args; } /** * Return a new panels context based on an existing context */ function nodereference_node_from_noderef_context($context = NULL, $conf) { // If unset it wants a generic, unfilled context, which is just NULL if (empty($context->data)) { return panels_context_create_empty('node', NULL); } if (isset($context->data->{$conf['field_name']}[0]['nid']) && ($nid = $context->data->{$conf['field_name']}[0]['nid'])) { if ($node = node_load($nid)) { return panels_context_create('node', $node); } } } /** * Settings form for the panels relationship. */ function nodereference_node_from_noderef_settings_form($conf) { $options = array(); foreach (content_fields() as $field) { if ($field['type'] == 'nodereference') { $options[$field['field_name']] = t($field['widget']['label']); } } $form['field_name'] = array( '#title' => t('Node reference field'), '#type' => 'select', '#options' => $options, '#default_value' => $conf['field_name'], '#prefix' => '
', '#suffix' => '
', ); return $form; }