CCK: Defines a field type for referencing one node from another. Note: Requires content.module.'); } } /** * Implementation of hook_menu(). */ function nodereference_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array('path' => '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' => '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' => 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_view'] = array( '#type' => 'select', '#title' => t('Advanced - Nodes that can be referenced'), '#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 :'. ''), ); } } return $form; case 'save': $settings = array('referenceable_types'); if (module_exists('views')) { $settings[] = 'advanced_view'; } 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 'view': foreach ($items as $delta => $item) { $items[$delta]['view'] = content_format($field, $item, 'default', $node); } return theme('field', $node, $field, $items, $teaser, $page); } } /** * Implementation of hook_field_formatter_info(). */ function nodereference_field_formatter_info() { return array( 'default' => array( 'label' => 'Default', 'field types' => array('nodereference'), ), 'plain' => array( 'label' => 'Plain text', 'field types' => array('nodereference'), ), ); } /** * Implementation of hook_field_formatter(). */ function nodereference_field_formatter($field, $item, $formatter, $node) { $text = ''; if (!empty($item['nid'])) { $referenced_node = node_load($item['nid']); if ($referenced_node) { $text = l($referenced_node->title, 'node/'. $referenced_node->nid); } } switch ($formatter) { case 'plain': return strip_tags($text); default: return $text; } } /** * Implementation of hook_widget_info(). */ function nodereference_widget_info() { return array( 'nodereference_select' => array( 'label' => 'Select List', 'field types' => array('nodereference'), ), 'nodereference_autocomplete' => array( 'label' => 'Autocomplete Text Field', 'field types' => array('nodereference'), ), ); } /** * Implementation of hook_widget(). */ function nodereference_widget($op, &$node, $field, &$node_field) { if ($field['widget']['type'] == 'nodereference_select') { switch ($op) { case 'prepare form values': $node_field_transposed = content_transpose_array_rows_cols($node_field); $node_field['default nids'] = $node_field_transposed['nid']; break; case 'form': $form = array(); $options = _nodereference_potential_references($field, true); foreach ($options as $key => $value) { $options[$key] = _nodereference_item($field, $value); } 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' => $node_field['default nids'], '#multiple' => $field['multiple'], '#options' => $options, '#required' => $field['required'], '#description' => $field['widget']['description'], ); return $form; case 'process form values': if ($field['multiple']) { if (empty($node_field['nids'])) { // if nothing selected, make it 'none' $node_field['nids'] = array('0'); } if (count($node_field['nids']) > 1) { // drop the 'none' options if other items were also selected unset($node_field['nids'][0]); } $node_field = content_transpose_array_rows_cols(array('nid' => $node_field['nids'])); } else { $node_field[0]['nid'] = $node_field['nids']; } // Remove the widget's data representation so it isn't saved. unset($node_field['nids']); } } else { switch ($op) { case 'prepare form values': foreach ($node_field as $delta => $item) { if (!empty($node_field[$delta]['nid'])) { $node_field[$delta]['default node_name'] = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $node_field[$delta]['nid'])); $node_field[$delta]['default node_name'] .= ' [nid:'. $node_field[$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']]['#title'] = t($field['widget']['label']); $form[$field['field_name']]['#description'] = $field['widget']['description']; $delta = 0; foreach ($node_field as $item) { if ($item['nid']) { $form[$field['field_name']][$delta]['node_name'] = array( '#type' => 'textfield', '#title' => '', '#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' => '', '#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' => $node_field[0]['default node_name'], '#required' => $field['required'], '#description' => $field['widget']['description'], ); } return $form; case 'validate': foreach ($node_field 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; $refs = _nodereference_potential_references($field, TRUE); if (!in_array($nid, array_keys($refs))) { form_set_error($error_field, t('This post can\'t be referenced.')); } elseif (!empty($title) && ($n = $refs[$nid]) && $title != $n->node_title) { form_set_error($error_field, t('Title mismatch. Please reiterate your selection.')); } } else { // no explicit nid $refs = _nodereference_potential_references($field, FALSE, $item['node_name'], TRUE); if (empty($refs)) { form_set_error($error_field, t('No post with that title can be referenced.')); } } } } return; case 'process form values': foreach ($node_field 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 = array_shift(array_keys($nids)); } } if (!empty($nid)) { $node_field[$delta]['nid'] = $nid; // Remove the widget's data representation so it isn't saved. unset($node_field[$delta]['node_name']); } else { // Don't save empty fields when they're not the first value (keep '0' otherwise) if ($delta > 0) {unset($node_field[$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') && ($view = views_get_view($field['advanced_view']))) { // advanced field : referenceable nodes defined by a view // let views.module build the query 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); $view_result = views_build_view('result', $view); $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[] = " type = '%s'"; $args[] = $related_type; } } } $related_clause = implode(' OR ', $related_types); if (!count($related_types)) { return array(); } if (isset($string)) { $string_clause = $exact_string ? " AND title = '%s'" : " AND 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, TRUE); } print drupal_to_js($matches); exit(); } function _nodereference_item($field, $item, $html = false) { if (module_exists('views') && ($view = views_get_view($field['advanced_view']))) { $output = theme('nodereference_item_advanced', $item, $view); if (!$html) { $output = strip_tags($output); } } else { $output = theme('nodereference_item_simple', $item); } 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 users to filter on. */ function _nodereference_filter_handler($op, $filterinfo) { $options = array('' => ''); $options = $options + _nodereference_potential_references($filterinfo['extra']['field']); return $options; }