1<?php
2// $Id$
3
4/**
5 * @file
6 * Defines a field type for referencing one node from another.
7 */
8
9/**
10 * Implementation of hook_menu().
11 */
12function nodereference_menu() {
13  $items = array();
14  $items['nodereference/autocomplete'] = array(
15    'title' => 'Nodereference autocomplete',
16    'page callback' => 'nodereference_autocomplete',
17    'access callback' => 'nodereference_autocomplete_access',
18    'access arguments' => array(2),
19    'type' => MENU_CALLBACK
20  );
21  return $items;
22}
23
24/**
25 * Implementation of hook_theme().
26 */
27function nodereference_theme() {
28  return array(
29    'nodereference_select' => array(
30      'arguments' => array('element' => NULL),
31    ),
32    'nodereference_buttons' => array(
33      'arguments' => array('element' => NULL),
34    ),
35    'nodereference_autocomplete' => array(
36      'arguments' => array('element' => NULL),
37    ),
38    'nodereference_formatter_default' => array(
39      'arguments' => array('element'),
40    ),
41    'nodereference_formatter_plain' => array(
42      'arguments' => array('element'),
43    ),
44    'nodereference_formatter_full' => array(
45      'arguments' => array('element'),
46      'function' => 'theme_nodereference_formatter_full_teaser',
47    ),
48    'nodereference_formatter_teaser' => array(
49      'arguments' => array('element'),
50      'function' => 'theme_nodereference_formatter_full_teaser',
51    ),
52  );
53}
54
55/**
56 * Implementaion of hook_ctools_plugin_directory().
57 */
58function nodereference_ctools_plugin_directory($module, $plugin) {
59  if ($module == 'ctools' && $plugin == 'relationships') {
60    return 'panels/' . $plugin;
61  }
62}
63
64/**
65 * Implementation of hook_field_info().
66 */
67function nodereference_field_info() {
68  return array(
69    'nodereference' => array(
70      'label' => t('Node reference'),
71      'description' => t('Store the ID of a related node as an integer value.'),
72//      'content_icon' => 'icon_content_noderef.png',
73    ),
74  );
75}
76
77/**
78 * Implementation of hook_field_settings().
79 */
80function nodereference_field_settings($op, $field) {
81  switch ($op) {
82    case 'form':
83      $form = array();
84      $form['referenceable_types'] = array(
85        '#type' => 'checkboxes',
86        '#title' => t('Content types that can be referenced'),
87        '#multiple' => TRUE,
88        '#default_value' => is_array($field['referenceable_types']) ? $field['referenceable_types'] : array(),
89        '#options' => array_map('check_plain', node_get_types('names')),
90      );
91      if (module_exists('views')) {
92        $views = array('--' => '--');
93        $all_views = views_get_all_views();
94        foreach ($all_views as $view) {
95          // Only 'node' views that have fields will work for our purpose.
96          if ($view->base_table == 'node' && !empty($view->display['default']->display_options['fields'])) {
97            if ($view->type == 'Default') {
98              $views[t('Default Views')][$view->name] = $view->name;
99            }
100            else {
101              $views[t('Existing Views')][$view->name] = $view->name;
102            }
103          }
104        }
105
106        $form['advanced'] = array(
107           '#type' => 'fieldset',
108           '#title' => t('Advanced - Nodes that can be referenced (View)'),
109           '#collapsible' => TRUE,
110           '#collapsed' => !isset($field['advanced_view']) || $field['advanced_view'] == '--',
111         );
112        if (count($views) > 1) {
113          $form['advanced']['advanced_view'] = array(
114            '#type' => 'select',
115            '#title' => t('View used to select the nodes'),
116            '#options' => $views,
117            '#default_value' => isset($field['advanced_view']) ? $field['advanced_view'] : '--',
118            '#description' => t('<p>Choose the "Views module" view that selects the nodes that can be referenced.<br />Note:</p>') .
119              t('<ul><li>Only views that have fields will work for this purpose.</li><li>This will discard the "Content types" settings above. Use the view\'s "filters" section instead.</li><li>Use the view\'s "fields" section to display additional informations about candidate nodes on node creation/edition form.</li><li>Use the view\'s "sort criteria" section to determine the order in which candidate nodes will be displayed.</li></ul>'),
120          );
121          $form['advanced']['advanced_view_args'] = array(
122            '#type' => 'textfield',
123            '#title' => t('View arguments'),
124            '#default_value' => isset($field['advanced_view_args']) ? $field['advanced_view_args'] : '',
125            '#required' => FALSE,
126            '#description' => t('Provide a comma separated list of arguments to pass to the view.'),
127          );
128        }
129        else {
130          $form['advanced']['no_view_help'] = array(
131            '#value' => t('<p>The list of nodes that can be referenced can be based on a "Views module" view but no appropriate views were found. <br />Note:</p>') .
132              t('<ul><li>Only views that have fields will work for this purpose.</li><li>This will discard the "Content types" settings above. Use the view\'s "filters" section instead.</li><li>Use the view\'s "fields" section to display additional informations about candidate nodes on node creation/edition form.</li><li>Use the view\'s "sort criteria" section to determine the order in which candidate nodes will be displayed.</li></ul>'),
133          );
134        }
135      }
136      return $form;
137
138    case 'save':
139      $settings = array('referenceable_types');
140      if (module_exists('views')) {
141        $settings[] = 'advanced_view';
142        $settings[] = 'advanced_view_args';
143      }
144      return $settings;
145
146    case 'database columns':
147      $columns = array(
148        'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, 'index' => TRUE),
149      );
150      return $columns;
151
152    case 'views data':
153      $data = content_views_field_views_data($field);
154      $db_info = content_database_info($field);
155      $table_alias = content_views_tablename($field);
156
157      // Filter: swap the handler to the 'in' operator.
158      $data[$table_alias][$field['field_name'] .'_nid']['filter']['handler'] = 'content_handler_filter_many_to_one';
159      // Argument: use node.title for summaries.
160      $data["node_$table_alias"]['table']['join']['node'] = array(
161        'table' => 'node',
162        'field' => 'nid',
163        'left_table' => $table_alias,
164        'left_field' => $field['field_name'] .'_nid',
165      );
166      $data[$table_alias][$field['field_name'] .'_nid']['argument']['handler'] = 'content_handler_argument_reference';
167      $data[$table_alias][$field['field_name'] .'_nid']['argument']['name table'] = "node_$table_alias";
168      $data[$table_alias][$field['field_name'] .'_nid']['argument']['name field'] = 'title';
169      // Relationship: add a relationship for related node.
170      $data[$table_alias][$field['field_name'] .'_nid']['relationship'] = array(
171        'base' => 'node',
172        'field' => $db_info['columns']['nid']['column'],
173        'handler' => 'content_handler_relationship',
174        'label' => t($field['widget']['label']),
175        'content_field_name' => $field['field_name'],
176      );
177      return $data;
178  }
179}
180
181/**
182 * Implementation of hook_field().
183 */
184function nodereference_field($op, &$node, $field, &$items, $teaser, $page) {
185  static $sanitized_nodes = array();
186
187  switch ($op) {
188    // When preparing a translation, load any translations of existing references.
189    case 'prepare translation':
190      $addition = array();
191      $addition[$field['field_name']] = array();
192      if (isset($node->translation_source->$field['field_name']) && is_array($node->translation_source->$field['field_name'])) {
193        foreach ($node->translation_source->$field['field_name'] as $key => $reference) {
194          $reference_node = node_load($reference['nid']);
195          // Test if the referenced node type is translatable and, if so,
196          // load translations if the reference is not for the current language.
197          // We can assume the translation module is present because it invokes 'prepare translation'.
198          if (translation_supported_type($reference_node->type) && !empty($reference_node->language) && $reference_node->language != $node->language && $translations = translation_node_get_translations($reference_node->tnid)) {
199            // If there is a translation for the current language, use it.
200            $addition[$field['field_name']][] = array(
201              'nid' => isset($translations[$node->language]) ? $translations[$node->language]->nid : $reference['nid'],
202            );
203          }
204        }
205      }
206      return $addition;
207
208    case 'validate':
209      // Extract nids to check.
210      $ids = array();
211      foreach ($items as $delta => $item) {
212        if (is_array($item) && !empty($item['nid'])) {
213          if (is_numeric($item['nid'])) {
214            $ids[] = $item['nid'];
215          }
216          else {
217            $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
218            if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
219            form_set_error($error_element, t("%name: invalid input.", array('%name' => t($field['widget']['label']))));
220          }
221        }
222      }
223      // Prevent performance hog if there are no ids to check.
224      if ($ids) {
225        $refs = _nodereference_potential_references($field, '', NULL, $ids);
226        foreach ($items as $delta => $item) {
227          if (is_array($item)) {
228            $error_element = isset($item['_error_element']) ? $item['_error_element'] : '';
229            if (is_array($item) && isset($item['_error_element'])) unset($item['_error_element']);
230            if (!empty($item['nid']) && !isset($refs[$item['nid']])) {
231              form_set_error($error_element, t("%name: this post can't be referenced.", array('%name' => t($field['widget']['label']))));
232            }
233          }
234        }
235      }
236      return $items;
237
238    case 'sanitize':
239      // We can't just check the node is 'referenceable', because Views-mode
240      // could rely on 'current user' (at edit time).
241
242      // Extract nids to check.
243      $ids = array();
244      foreach ($items as $delta => $item) {
245        if (is_array($item)) {
246          // Default to 'non accessible'.
247          $items[$delta]['safe'] = array();
248          if (!empty($item['nid']) && is_numeric($item['nid'])) {
249            $ids[] = $item['nid'];
250          }
251        }
252      }
253      if ($ids) {
254        // Load information about nids that we haven't already loaded during
255        // this page request.
256        $missing_ids = array_diff($ids, array_keys($sanitized_nodes));
257        if (!empty($missing_ids)) {
258          $where = array('n.nid in ('. db_placeholders($missing_ids) . ')');
259          if (!user_access('administer nodes')) {
260            $where[] = 'n.status = 1';
261          }
262          $result = db_query(db_rewrite_sql('SELECT n.nid, n.title, n.status FROM {node} n WHERE '. implode(' AND ', $where)), $missing_ids);
263          while ($row = db_fetch_array($result)) {
264            $sanitized_nodes[$row['nid']] = $row;
265          }
266        }
267        foreach ($items as $delta => $item) {
268          if (is_array($item) && !empty($item['nid']) && isset($sanitized_nodes[$item['nid']])) {
269            $items[$delta]['safe'] = $sanitized_nodes[$item['nid']];
270          }
271        }
272      }
273      return $items;
274  }
275}
276
277/**
278 * Implementation of hook_content_is_empty().
279 */
280function nodereference_content_is_empty($item, $field) {
281  if (empty($item['nid'])) {
282    return TRUE;
283  }
284  return FALSE;
285}
286
287/**
288 * Implementation of hook_field_formatter_info().
289 */
290function nodereference_field_formatter_info() {
291  return array(
292    'default' => array(
293      'label' => t('Title (link)'),
294      'field types' => array('nodereference'),
295      'multiple values' => CONTENT_HANDLE_CORE,
296    ),
297    'plain' => array(
298      'label' => t('Title (no link)'),
299      'field types' => array('nodereference'),
300      'multiple values' => CONTENT_HANDLE_CORE,
301    ),
302    'full' => array(
303      'label' => t('Full node'),
304      'field types' => array('nodereference'),
305      'multiple values' => CONTENT_HANDLE_CORE,
306    ),
307    'teaser' => array(
308      'label' => t('Teaser'),
309      'field types' => array('nodereference'),
310      'multiple values' => CONTENT_HANDLE_CORE,
311    ),
312  );
313}
314
315/**
316 * Theme function for 'default' nodereference field formatter.
317 */
318function theme_nodereference_formatter_default($element) {
319  $output = '';
320  if (!empty($element['#item']['safe']['nid'])) {
321    $output = l($element['#item']['safe']['title'], 'node/'. $element['#item']['safe']['nid']);
322    if (!$element['#item']['safe']['status']) {
323      $output = '<span class="node-unpublished"> '. t('(Unpublished)') ." $output</span>";
324    }
325  }
326  return $output;
327}
328
329/**
330 * Theme function for 'plain' nodereference field formatter.
331 */
332function theme_nodereference_formatter_plain($element) {
333  $output = '';
334  if (!empty($element['#item']['safe']['nid'])) {
335    $output = check_plain($element['#item']['safe']['title']);
336    if (!$element['#item']['safe']['status']) {
337      $output = '<span class="node-unpublished"> '. t('(Unpublished)') ." $output</span>";
338    }
339  }
340  return $output;
341}
342
343/**
344 * Proxy theme function for 'full' and 'teaser' nodereference field formatters.
345 */
346function theme_nodereference_formatter_full_teaser($element) {
347  static $recursion_queue = array();
348  $output = '';
349  if (!empty($element['#item']['safe']['nid'])) {
350    $nid = $element['#item']['safe']['nid'];
351    $node = $element['#node'];
352    $field = content_fields($element['#field_name'], $element['#type_name']);
353    // If no 'referencing node' is set, we are starting a new 'reference thread'
354    if (!isset($node->referencing_node)) {
355      $recursion_queue = array();
356    }
357    $recursion_queue[] = $node->nid;
358    if (in_array($nid, $recursion_queue)) {
359      // Prevent infinite recursion caused by reference cycles:
360      // if the node has already been rendered earlier in this 'thread',
361      // we fall back to 'default' (node title) formatter.
362      return theme('nodereference_formatter_default', $element);
363    }
364    if ($referenced_node = node_load($nid)) {
365      $referenced_node->referencing_node = $node;
366      $referenced_node->referencing_field = $field;
367      $output = node_view($referenced_node, $element['#formatter'] == 'teaser');
368    }
369  }
370  return $output;
371}
372
373/**
374 * Helper function for formatters.
375 *
376 * Store node titles collected in the curent request.
377 */
378function _nodereference_titles($nid, $known_title = NULL) {
379  static $titles = array();
380  if (!isset($titles[$nid])) {
381    $title = $known_title ? $known_title : db_result(db_query(db_rewrite_sql("SELECT n.title FROM {node} n WHERE n.nid=%d"), $nid));
382    $titles[$nid] = $title ? $title : '';
383  }
384  return $titles[$nid];
385}
386
387/**
388 * Implementation of hook_widget_info().
389 *
390 * We need custom handling of multiple values for the nodereference_select
391 * widget because we need to combine them into a options list rather
392 * than display multiple elements.
393 *
394 * We will use the content module's default handling for default value.
395 *
396 * Callbacks can be omitted if default handing is used.
397 * They're included here just so this module can be used
398 * as an example for custom modules that might do things
399 * differently.
400 */
401function nodereference_widget_info() {
402  return array(
403    'nodereference_select' => array(
404      'label' => t('Select list'),
405      'field types' => array('nodereference'),
406      'multiple values' => CONTENT_HANDLE_MODULE,
407      'callbacks' => array(
408        'default value' => CONTENT_CALLBACK_DEFAULT,
409      ),
410    ),
411    'nodereference_buttons' => array(
412      'label' => t('Check boxes/radio buttons'),
413      'field types' => array('nodereference'),
414      'multiple values' => CONTENT_HANDLE_MODULE,
415      'callbacks' => array(
416        'default value' => CONTENT_CALLBACK_DEFAULT,
417      ),
418    ),
419    'nodereference_autocomplete' => array(
420      'label' => t('Autocomplete text field'),
421      'field types' => array('nodereference'),
422      'multiple values' => CONTENT_HANDLE_CORE,
423      'callbacks' => array(
424        'default value' => CONTENT_CALLBACK_DEFAULT,
425      ),
426    ),
427  );
428}
429
430/**
431 * Implementation of FAPI hook_elements().
432 *
433 * Any FAPI callbacks needed for individual widgets can be declared here,
434 * and the element will be passed to those callbacks for processing.
435 *
436 * Drupal will automatically theme the element using a theme with
437 * the same name as the hook_elements key.
438 *
439 * Autocomplete_path is not used by text_widget but other widgets can use it
440 * (see nodereference and userreference).
441 */
442function nodereference_elements() {
443  return array(
444    'nodereference_select' => array(
445      '#input' => TRUE,
446      '#columns' => array('uid'), '#delta' => 0,
447      '#process' => array('nodereference_select_process'),
448    ),
449    'nodereference_buttons' => array(
450      '#input' => TRUE,
451      '#columns' => array('uid'), '#delta' => 0,
452      '#process' => array('nodereference_buttons_process'),
453    ),
454    'nodereference_autocomplete' => array(
455      '#input' => TRUE,
456      '#columns' => array('name'), '#delta' => 0,
457      '#process' => array('nodereference_autocomplete_process'),
458      '#autocomplete_path' => FALSE,
459      ),
460    );
461}
462
463/**
464 * Implementation of hook_widget_settings().
465 */
466function nodereference_widget_settings($op, $widget) {
467  switch ($op) {
468    case 'form':
469      $form = array();
470      $match = isset($widget['autocomplete_match']) ? $widget['autocomplete_match'] : 'contains';
471      $size = (isset($widget['size']) && is_numeric($widget['size'])) ? $widget['size'] : 60;
472      if ($widget['type'] == 'nodereference_autocomplete') {
473        $form['autocomplete_match'] = array(
474          '#type' => 'select',
475          '#title' => t('Autocomplete matching'),
476          '#default_value' => $match,
477          '#options' => array(
478            'starts_with' => t('Starts with'),
479            'contains' => t('Contains'),
480          ),
481          '#description' => t('Select the method used to collect autocomplete suggestions. Note that <em>Contains</em> can cause performance issues on sites with thousands of nodes.'),
482        );
483        $form['size'] = array(
484          '#type' => 'textfield',
485          '#title' => t('Size of textfield'),
486          '#default_value' => $size,
487          '#element_validate' => array('_element_validate_integer_positive'),
488          '#required' => TRUE,
489        );
490      }
491      else {
492        $form['autocomplete_match'] = array('#type' => 'hidden', '#value' => $match);
493        $form['size'] = array('#type' => 'hidden', '#value' => $size);
494      }
495      return $form;
496
497    case 'save':
498      return array('autocomplete_match', 'size');
499  }
500}
501
502/**
503 * Implementation of hook_widget().
504 *
505 * Attach a single form element to the form. It will be built out and
506 * validated in the callback(s) listed in hook_elements. We build it
507 * out in the callbacks rather than here in hook_widget so it can be
508 * plugged into any module that can provide it with valid
509 * $field information.
510 *
511 * Content module will set the weight, field name and delta values
512 * for each form element. This is a change from earlier CCK versions
513 * where the widget managed its own multiple values.
514 *
515 * If there are multiple values for this field, the content module will
516 * call this function as many times as needed.
517 *
518 * @param $form
519 *   the entire form array, $form['#node'] holds node information
520 * @param $form_state
521 *   the form_state, $form_state['values'][$field['field_name']]
522 *   holds the field's form values.
523 * @param $field
524 *   the field array
525 * @param $items
526 *   array of default values for this field
527 * @param $delta
528 *   the order of this item in the array of subelements (0, 1, 2, etc)
529 *
530 * @return
531 *   the form item for a single element for this field
532 */
533function nodereference_widget(&$form, &$form_state, $field, $items, $delta = 0) {
534  switch ($field['widget']['type']) {
535    case 'nodereference_select':
536      $element = array(
537        '#type' => 'nodereference_select',
538        '#default_value' => $items,
539      );
540      break;
541
542    case 'nodereference_buttons':
543      $element = array(
544        '#type' => 'nodereference_buttons',
545        '#default_value' => $items,
546      );
547      break;
548
549    case 'nodereference_autocomplete':
550      $element = array(
551        '#type' => 'nodereference_autocomplete',
552        '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL,
553        '#value_callback' => 'nodereference_autocomplete_value',
554      );
555      break;
556  }
557  return $element;
558}
559
560/**
561 * Value for a nodereference autocomplete element.
562 *
563 * Substitute in the node title for the node nid.
564 */
565function nodereference_autocomplete_value($element, $edit = FALSE) {
566  $field_key  = $element['#columns'][0];
567  if (!empty($element['#default_value'][$field_key])) {
568    $nid = $element['#default_value'][$field_key];
569    $value = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $nid));
570    $value .= ' [nid:'. $nid .']';
571    return array($field_key => $value);
572  }
573  return array($field_key => NULL);
574}
575
576/**
577 * Process an individual element.
578 *
579 * Build the form element. When creating a form using FAPI #process,
580 * note that $element['#value'] is already set.
581 *
582 * The $fields array is in $form['#field_info'][$element['#field_name']].
583 */
584function nodereference_select_process($element, $edit, $form_state, $form) {
585  // The nodereference_select widget doesn't need to create its own
586  // element, it can wrap around the optionwidgets_select element.
587  // This will create a new, nested instance of the field.
588  // Add a validation step where the value can be unwrapped.
589  $field_key  = $element['#columns'][0];
590  $element[$field_key] = array(
591    '#type' => 'optionwidgets_select',
592    '#default_value' => isset($element['#value']) ? $element['#value'] : '',
593    // The following values were set by the content module and need
594    // to be passed down to the nested element.
595    '#title' => $element['#title'],
596    '#required' => $element['#required'],
597    '#description' => $element['#description'],
598    '#field_name' => $element['#field_name'],
599    '#type_name' => $element['#type_name'],
600    '#delta' => $element['#delta'],
601    '#columns' => $element['#columns'],
602  );
603  if (empty($element[$field_key]['#element_validate'])) {
604    $element[$field_key]['#element_validate'] = array();
605  }
606  array_unshift($element[$field_key]['#element_validate'], 'nodereference_optionwidgets_validate');
607  return $element;
608}
609
610/**
611 * Process an individual element.
612 *
613 * Build the form element. When creating a form using FAPI #process,
614 * note that $element['#value'] is already set.
615 *
616 * The $fields array is in $form['#field_info'][$element['#field_name']].
617 */
618function nodereference_buttons_process($element, $edit, $form_state, $form) {
619  // The nodereference_select widget doesn't need to create its own
620  // element, it can wrap around the optionwidgets_select element.
621  // This will create a new, nested instance of the field.
622  // Add a validation step where the value can be unwrapped.
623  $field_key  = $element['#columns'][0];
624  $element[$field_key] = array(
625    '#type' => 'optionwidgets_buttons',
626    '#default_value' => isset($element['#value']) ? $element['#value'] : '',
627    // The following values were set by the content module and need
628    // to be passed down to the nested element.
629    '#title' => $element['#title'],
630    '#required' => $element['#required'],
631    '#description' => $element['#description'],
632    '#field_name' => $element['#field_name'],
633    '#type_name' => $element['#type_name'],
634    '#delta' => $element['#delta'],
635    '#columns' => $element['#columns'],
636  );
637  if (empty($element[$field_key]['#element_validate'])) {
638    $element[$field_key]['#element_validate'] = array();
639  }
640  array_unshift($element[$field_key]['#element_validate'], 'nodereference_optionwidgets_validate');
641  return $element;
642}
643
644/**
645 * Process an individual element.
646 *
647 * Build the form element. When creating a form using FAPI #process,
648 * note that $element['#value'] is already set.
649 *
650 */
651function nodereference_autocomplete_process($element, $edit, $form_state, $form) {
652
653  // The nodereference autocomplete widget doesn't need to create its own
654  // element, it can wrap around the text_textfield element and add an autocomplete
655  // path and some extra processing to it.
656  // Add a validation step where the value can be unwrapped.
657  $field_key  = $element['#columns'][0];
658
659  $element[$field_key] = array(
660    '#type' => 'text_textfield',
661    '#default_value' => isset($element['#value']) ? $element['#value'] : '',
662    '#autocomplete_path' => 'nodereference/autocomplete/'. $element['#field_name'],
663    // The following values were set by the content module and need
664    // to be passed down to the nested element.
665    '#title' => $element['#title'],
666    '#required' => $element['#required'],
667    '#description' => $element['#description'],
668    '#field_name' => $element['#field_name'],
669    '#type_name' => $element['#type_name'],
670    '#delta' => $element['#delta'],
671    '#columns' => $element['#columns'],
672  );
673  if (empty($element[$field_key]['#element_validate'])) {
674    $element[$field_key]['#element_validate'] = array();
675  }
676  array_unshift($element[$field_key]['#element_validate'], 'nodereference_autocomplete_validate');
677
678  // Used so that hook_field('validate') knows where to flag an error.
679  $element['_error_element'] = array(
680    '#type' => 'value',
681    // Wrapping the element around a text_textfield element creates a
682    // nested element, so the final id will look like 'field-name-0-nid-nid'.
683    '#value' => implode('][', array_merge($element['#parents'], array($field_key, $field_key))),
684  );
685  return $element;
686}
687
688/**
689 * Validate a select/buttons element.
690 *
691 * Remove the wrapper layer and set the right element's value.
692 * We don't know exactly where this element is, so we drill down
693 * through the element until we get to our key.
694 *
695 * We use $form_state['values'] instead of $element['#value']
696 * to be sure we have the most accurate value when other modules
697 * like optionwidgets are using #element_validate to alter the value.
698 */
699function nodereference_optionwidgets_validate($element, &$form_state) {
700  $field_key  = $element['#columns'][0];
701
702  $value = $form_state['values'];
703  $new_parents = array();
704  foreach ($element['#parents'] as $parent) {
705    $value = $value[$parent];
706    // Use === to be sure we get right results if parent is a zero (delta) value.
707    if ($parent === $field_key) {
708      $element['#parents'] = $new_parents;
709      form_set_value($element, $value, $form_state);
710      break;
711    }
712    $new_parents[] = $parent;
713  }
714}
715
716/**
717 * Validate an autocomplete element.
718 *
719 * Remove the wrapper layer and set the right element's value.
720 * This will move the nested value at 'field-name-0-nid-nid'
721 * back to its original location, 'field-name-0-nid'.
722 */
723function nodereference_autocomplete_validate($element, &$form_state) {
724  $field_name = $element['#field_name'];
725  $type_name = $element['#type_name'];
726  $field = content_fields($field_name, $type_name);
727  $field_key  = $element['#columns'][0];
728  $delta = $element['#delta'];
729  $value = $element['#value'][$field_key];
730  $nid = NULL;
731  if (!empty($value)) {
732    preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $value, $matches);
733    if (!empty($matches)) {
734      // Explicit [nid:n].
735      list(, $title, $nid) = $matches;
736      if (!empty($title) && ($n = node_load($nid)) && trim($title) != trim($n->title)) {
737        form_error($element[$field_key], t('%name: title mismatch. Please check your selection.', array('%name' => t($field['widget']['label']))));
738      }
739    }
740    else {
741      // No explicit nid.
742      $reference = _nodereference_potential_references($field, $value, 'equals', NULL, 1);
743      if (empty($reference)) {
744        form_error($element[$field_key], t('%name: found no valid post with that title.', array('%name' => t($field['widget']['label']))));
745      }
746      else {
747        // TODO:
748        // the best thing would be to present the user with an additional form,
749        // allowing the user to choose between valid candidates with the same title
750        // ATM, we pick the first matching candidate...
751        $nid = key($reference);
752      }
753    }
754  }
755  form_set_value($element, $nid, $form_state);
756}
757
758/**
759 * Implementation of hook_allowed_values().
760 */
761function nodereference_allowed_values($field) {
762  $references = _nodereference_potential_references($field);
763
764  $options = array();
765  foreach ($references as $key => $value) {
766    $options[$key] = $value['rendered'];
767  }
768
769  return $options;
770}
771
772/**
773 * Fetch an array of all candidate referenced nodes.
774 *
775 * This info is used in various places (allowed values, autocomplete results,
776 * input validation...). Some of them only need the nids, others nid + titles,
777 * others yet nid + titles + rendered row (for display in widgets).
778 * The array we return contains all the potentially needed information, and lets
779 * consumers use the parts they actually need.
780 *
781 * @param $field
782 *   The field description.
783 * @param $string
784 *   Optional string to filter titles on (used by autocomplete).
785 * @param $match
786 *   Operator to match filtered name against, can be any of:
787 *   'contains', 'equals', 'starts_with'
788 * @param $ids
789 *   Optional node ids to lookup (the $string and $match arguments will be
790 *   ignored).
791 * @param $limit
792 *   If non-zero, limit the size of the result set.
793 *
794 * @return
795 *   An array of valid nodes in the form:
796 *   array(
797 *     nid => array(
798 *       'title' => The node title,
799 *       'rendered' => The text to display in widgets (can be HTML)
800 *     ),
801 *     ...
802 *   )
803 */
804function _nodereference_potential_references($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
805  static $results = array();
806
807  // Create unique id for static cache.
808  $cid = $field['field_name'] .':'. $match .':'. ($string !== '' ? $string : implode('-', $ids)) .':'. $limit;
809  if (!isset($results[$cid])) {
810    $references = FALSE;
811    if (module_exists('views') && !empty($field['advanced_view']) && $field['advanced_view'] != '--') {
812      $references = _nodereference_potential_references_views($field, $string, $match, $ids, $limit);
813    }
814    // If the view doesn't exist, we got FALSE, and fallback to the regular 'standard mode'.
815
816    if ($references === FALSE) {
817      $references = _nodereference_potential_references_standard($field, $string, $match, $ids, $limit);
818    }
819
820    // Store the results.
821    $results[$cid] = !empty($references) ? $references : array();
822  }
823
824  return $results[$cid];
825}
826
827/**
828 * Helper function for _nodereference_potential_references():
829 * case of Views-defined referenceable nodes.
830 */
831function _nodereference_potential_references_views($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
832  $view_name = $field['advanced_view'];
833
834  if ($view = views_get_view($view_name)) {
835    // We add a display, and let it derive from the 'default' display.
836    // TODO: We should let the user pick a display in the fields settings - sort of requires AHAH...
837    $display = $view->add_display('content_references');
838    $view->set_display($display);
839
840    // TODO from merlinofchaos on IRC : arguments using summary view can defeat the style setting.
841    // We might also need to check if there's an argument, and set *its* style_plugin as well.
842    $view->display_handler->set_option('style_plugin', 'content_php_array_autocomplete');
843    $view->display_handler->set_option('row_plugin', 'fields');
844    // Used in content_plugin_style_php_array::render(), to get
845    // the 'field' to be used as title.
846    $view->display_handler->set_option('content_title_field', 'title');
847
848    // Additional options to let content_plugin_display_references::query()
849    // narrow the results.
850    $options = array(
851      'table' => 'node',
852      'field_string' => 'title',
853      'string' => $string,
854      'match' => $match,
855      'field_id' => 'nid',
856      'ids' => $ids,
857    );
858    $view->display_handler->set_option('content_options', $options);
859
860    // TODO : for consistency, a fair amount of what's below
861    // should be moved to content_plugin_display_references
862
863    // Limit result set size.
864    $limit = isset($limit) ? $limit : 0;
865    $view->display_handler->set_option('items_per_page', $limit);
866
867    // Get arguments for the view.
868    if (!empty($field['advanced_view_args'])) {
869      // TODO: Support Tokens using token.module ?
870      $view_args = array_map('trim', explode(',', $field['advanced_view_args']));
871    }
872    else {
873      $view_args = array();
874    }
875
876    // We do need title field, so add it if not present (unlikely, but...)
877    $fields = $view->get_items('field', $display);
878    if (!isset($fields['title'])) {
879      $view->add_item($display, 'field', 'node', 'title');
880    }
881
882    // If not set, make all fields inline and define a separator.
883    $options = $view->display_handler->get_option('row_options');
884    if (empty($options['inline'])) {
885      $options['inline'] = drupal_map_assoc(array_keys($view->get_items('field', $display)));
886    }
887    if (empty($options['separator'])) {
888      $options['separator'] = '-';
889    }
890    $view->display_handler->set_option('row_options', $options);
891
892    // Make sure the query is not cached
893    $view->is_cacheable = FALSE;
894
895    // Get the results.
896    $result = $view->execute_display($display, $view_args);
897  }
898  else {
899    $result = FALSE;
900  }
901
902  return $result;
903}
904
905/**
906 * Helper function for _nodereference_potential_references():
907 * referenceable nodes defined by content types.
908 */
909function _nodereference_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) {
910  $related_types = array();
911  $where = array();
912  $args = array();
913
914  if (is_array($field['referenceable_types'])) {
915    foreach (array_filter($field['referenceable_types']) as $related_type) {
916      $related_types[] = "n.type = '%s'";
917      $args[] = $related_type;
918    }
919  }
920
921  $where[] = implode(' OR ', $related_types);
922
923  if (!count($related_types)) {
924    return array();
925  }
926
927  if ($string !== '') {
928    $like = $GLOBALS["db_type"] == 'pgsql' ? "ILIKE" : "LIKE";
929    $match_clauses = array(
930      'contains' => "$like '%%%s%%'",
931      'equals' => "= '%s'",
932      'starts_with' => "$like '%s%%'",
933    );
934    $where[] = 'n.title '. (isset($match_clauses[$match]) ? $match_clauses[$match] : $match_clauses['contains']);
935    $args[] = $string;
936  }
937  elseif ($ids) {
938    $where[] = 'n.nid IN (' . db_placeholders($ids) . ')';
939    $args = array_merge($args, $ids);
940  }
941
942  $where_clause = $where ? 'WHERE ('. implode(') AND (', $where) .')' : '';
943  $sql = db_rewrite_sql("SELECT n.nid, n.title AS node_title, n.type AS node_type FROM {node} n $where_clause ORDER BY n.title, n.type");
944  $result = $limit ? db_query_range($sql, $args, 0, $limit) : db_query($sql, $args);
945  $references = array();
946  while ($node = db_fetch_object($result)) {
947    $references[$node->nid] = array(
948      'title' => $node->node_title,
949      'rendered' => check_plain($node->node_title),
950    );
951  }
952
953  return $references;
954}
955
956/**
957 * Check access to the menu callback of the autocomplete widget.
958 *
959 * Check for both 'edit' and 'view' access in the unlikely event
960 * a user has edit but not view access.
961 */
962function nodereference_autocomplete_access($field_name) {
963  return user_access('access content') && ($field = content_fields($field_name)) && isset($field['field_name']) && content_access('view', $field) && content_access('edit', $field);
964}
965
966/**
967 * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users
968 */
969function nodereference_autocomplete($field_name, $string = '') {
970  $fields = content_fields();
971  $field = $fields[$field_name];
972  $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains';
973  $matches = array();
974
975  $references = _nodereference_potential_references($field, $string, $match, array(), 10);
976  foreach ($references as $id => $row) {
977    // Add a class wrapper for a few required CSS overrides.
978    $matches[$row['title'] ." [nid:$id]"] = '<div class="reference-autocomplete">'. $row['rendered'] . '</div>';
979  }
980  drupal_json($matches);
981}
982
983/**
984 * Implementation of hook_node_types.
985 */
986function nodereference_node_type($op, $info) {
987  switch ($op) {
988    case 'update':
989      // Reflect type name changes to the 'referenceable types' settings.
990      if (!empty($info->old_type) && $info->old_type != $info->type) {
991        // content.module's implementaion of hook_node_type() has already
992        // refreshed _content_type_info().
993        $fields = content_fields();
994        $rebuild = FALSE;
995        foreach ($fields as $field_name => $field) {
996          if ($field['type'] == 'nodereference' && isset($field['referenceable_types'][$info->old_type])) {
997            $field['referenceable_types'][$info->type] = empty($field['referenceable_types'][$info->old_type]) ? 0 : $info->type;
998            unset($field['referenceable_types'][$info->old_type]);
999            content_field_instance_update($field, FALSE);
1000            $rebuild = TRUE;
1001          }
1002        }
1003
1004        // Clear caches and rebuild menu only if any field has been updated.
1005        if ($rebuild) {
1006          content_clear_type_cache(TRUE);
1007          menu_rebuild();
1008        }
1009      }
1010      break;
1011  }
1012}
1013
1014/**
1015 * Theme preprocess function.
1016 *
1017 * Allows specific node templates for nodes displayed as values of a
1018 * nodereference field with the 'full node' / 'teaser' formatters.
1019 */
1020function nodereference_preprocess_node(&$vars) {
1021  // The 'referencing_field' attribute of the node is added by the 'teaser'
1022  // and 'full node' formatters.
1023  if (!empty($vars['node']->referencing_field)) {
1024    $node = $vars['node'];
1025    $field = $node->referencing_field;
1026    $vars['template_files'][] = 'node-nodereference';
1027    $vars['template_files'][] = 'node-nodereference-'. $field['field_name'];
1028    $vars['template_files'][] = 'node-nodereference-'. $node->type;
1029    $vars['template_files'][] = 'node-nodereference-'. $field['field_name'] .'-'. $node->type;
1030  }
1031}
1032
1033/**
1034 * FAPI theme for an individual elements.
1035 *
1036 * The textfield or select is already rendered by the
1037 * textfield or select themes and the html output
1038 * lives in $element['#children']. Override this theme to
1039 * make custom changes to the output.
1040 *
1041 * $element['#field_name'] contains the field name
1042 * $element['#delta]  is the position of this element in the group
1043 */
1044function theme_nodereference_select($element) {
1045  return $element['#children'];
1046}
1047
1048function theme_nodereference_buttons($element) {
1049  return $element['#children'];
1050}
1051
1052function theme_nodereference_autocomplete($element) {
1053  return $element['#children'];
1054}
1055