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