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