| Commit | Line | Data |
|---|---|---|
| e1a2f8a6 JC |
1 | <?php |
| 2 | // $Id$ | |
| 3 | ||
| 4 | /** | |
| 5 | * @file | |
| 0f6ca9bb | 6 | * Defines a field type for referencing one node from another. |
| e1a2f8a6 JC |
7 | */ |
| 8 | ||
| 9 | /** | |
| 0f6ca9bb JC |
10 | * Implementation of hook_menu(). |
| 11 | */ | |
| 4a6e00e0 | 12 | function nodereference_menu() { |
| 0f6ca9bb | 13 | $items = array(); |
| 4a6e00e0 | 14 | $items['nodereference/autocomplete'] = array( |
| c56d4554 | 15 | 'title' => 'Nodereference autocomplete', |
| 4a6e00e0 | 16 | 'page callback' => 'nodereference_autocomplete', |
| d6d7c227 KS |
17 | 'access callback' => 'nodereference_autocomplete_access', |
| 18 | 'access arguments' => array(2), | |
| 4a6e00e0 YC |
19 | 'type' => MENU_CALLBACK |
| 20 | ); | |
| 0f6ca9bb JC |
21 | return $items; |
| 22 | } | |
| 23 | ||
| 24 | /** | |
| b01b0973 KS |
25 | * Implementation of hook_theme(). |
| 26 | */ | |
| 27 | function nodereference_theme() { | |
| 28 | return array( | |
| ea73d362 KS |
29 | 'nodereference_select' => array( |
| 30 | 'arguments' => array('element' => NULL), | |
| 31 | ), | |
| 578c7b49 YC |
32 | 'nodereference_buttons' => array( |
| 33 | 'arguments' => array('element' => NULL), | |
| 34 | ), | |
| ea73d362 KS |
35 | 'nodereference_autocomplete' => array( |
| 36 | 'arguments' => array('element' => NULL), | |
| 37 | ), | |
| e74ada9a YC |
38 | 'nodereference_formatter_default' => array( |
| 39 | 'arguments' => array('element'), | |
| 40 | ), | |
| 8f8522cd KS |
41 | 'nodereference_formatter_plain' => array( |
| 42 | 'arguments' => array('element'), | |
| 43 | ), | |
| e74ada9a YC |
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 | ), | |
| b01b0973 KS |
52 | ); |
| 53 | } | |
| 54 | ||
| 55 | /** | |
| aff1f800 YC |
56 | * Implementaion of hook_ctools_plugin_directory(). |
| 57 | */ | |
| 58 | function nodereference_ctools_plugin_directory($module, $plugin) { | |
| 59 | if ($module == 'ctools' && $plugin == 'relationships') { | |
| 60 | return 'panels/' . $plugin; | |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | /** | |
| e1a2f8a6 JC |
65 | * Implementation of hook_field_info(). |
| 66 | */ | |
| 67 | function nodereference_field_info() { | |
| 68 | return array( | |
| e9b6b501 | 69 | 'nodereference' => array( |
| 2400f882 KS |
70 | 'label' => t('Node reference'), |
| 71 | 'description' => t('Store the ID of a related node as an integer value.'), | |
| c5482d81 | 72 | // 'content_icon' => 'icon_content_noderef.png', |
| 6d7a097b | 73 | ), |
| e1a2f8a6 JC |
74 | ); |
| 75 | } | |
| 76 | ||
| 77 | /** | |
| 78 | * Implementation of hook_field_settings(). | |
| 79 | */ | |
| e2c6522a | 80 | function nodereference_field_settings($op, $field) { |
| e1a2f8a6 JC |
81 | switch ($op) { |
| 82 | case 'form': | |
| 83 | $form = array(); | |
| 1182d802 JC |
84 | $form['referenceable_types'] = array( |
| 85 | '#type' => 'checkboxes', | |
| 86 | '#title' => t('Content types that can be referenced'), | |
| 87 | '#multiple' => TRUE, | |
| 3973f7be | 88 | '#default_value' => is_array($field['referenceable_types']) ? $field['referenceable_types'] : array(), |
| 4b205056 | 89 | '#options' => array_map('check_plain', node_get_types('names')), |
| 1182d802 | 90 | ); |
| 63e0b10d KS |
91 | if (module_exists('views')) { |
| 92 | $views = array('--' => '--'); | |
| 93 | $all_views = views_get_all_views(); | |
| 94 | foreach ($all_views as $view) { | |
| e1655be5 YC |
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 | } | |
| a2ec9362 | 103 | } |
| 63e0b10d KS |
104 | } |
| 105 | ||
| 0e968023 YC |
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 | ); | |
| 63e0b10d | 112 | if (count($views) > 1) { |
| 63e0b10d KS |
113 | $form['advanced']['advanced_view'] = array( |
| 114 | '#type' => 'select', | |
| 3a2d35f5 | 115 | '#title' => t('View used to select the nodes'), |
| 63e0b10d KS |
116 | '#options' => $views, |
| 117 | '#default_value' => isset($field['advanced_view']) ? $field['advanced_view'] : '--', | |
| 0e968023 YC |
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>'), | |
| 63e0b10d KS |
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 | } | |
| 0e968023 YC |
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 | } | |
| 63e0b10d | 135 | } |
| e1a2f8a6 JC |
136 | return $form; |
| 137 | ||
| e1a2f8a6 | 138 | case 'save': |
| 0c2959db | 139 | $settings = array('referenceable_types'); |
| 63e0b10d KS |
140 | if (module_exists('views')) { |
| 141 | $settings[] = 'advanced_view'; | |
| 142 | $settings[] = 'advanced_view_args'; | |
| 143 | } | |
| 0c2959db | 144 | return $settings; |
| 2d3f00e8 | 145 | |
| bbbd884e JC |
146 | case 'database columns': |
| 147 | $columns = array( | |
| 972214b8 | 148 | 'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => FALSE, 'index' => TRUE), |
| 734116ed YC |
149 | ); |
| 150 | return $columns; | |
| 151 | ||
| b91d7f2b YC |
152 | case 'views data': |
| 153 | $data = content_views_field_views_data($field); | |
| 154 | $db_info = content_database_info($field); | |
| f33cc12c KS |
155 | $table_alias = content_views_tablename($field); |
| 156 | ||
| 47f94b0d | 157 | // Filter: swap the handler to the 'in' operator. |
| bea5ae87 | 158 | $data[$table_alias][$field['field_name'] .'_nid']['filter']['handler'] = 'content_handler_filter_many_to_one'; |
| 47f94b0d | 159 | // Argument: use node.title for summaries. |
| 017108ce YC |
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 | ); | |
| 2a63f940 | 166 | $data[$table_alias][$field['field_name'] .'_nid']['argument']['handler'] = 'content_handler_argument_reference'; |
| 017108ce | 167 | $data[$table_alias][$field['field_name'] .'_nid']['argument']['name table'] = "node_$table_alias"; |
| 0b20f95b | 168 | $data[$table_alias][$field['field_name'] .'_nid']['argument']['name field'] = 'title'; |
| 47f94b0d | 169 | // Relationship: add a relationship for related node. |
| f33cc12c | 170 | $data[$table_alias][$field['field_name'] .'_nid']['relationship'] = array( |
| 330807d0 | 171 | 'base' => 'node', |
| b91d7f2b | 172 | 'field' => $db_info['columns']['nid']['column'], |
| 0bb3edd5 | 173 | 'handler' => 'content_handler_relationship', |
| f458bc00 | 174 | 'label' => t($field['widget']['label']), |
| b6ca681f | 175 | 'content_field_name' => $field['field_name'], |
| b91d7f2b YC |
176 | ); |
| 177 | return $data; | |
| e1a2f8a6 JC |
178 | } |
| 179 | } | |
| 180 | ||
| 181 | /** | |
| 182 | * Implementation of hook_field(). | |
| 183 | */ | |
| 1fbe637d | 184 | function nodereference_field($op, &$node, $field, &$items, $teaser, $page) { |
| d6d7c227 KS |
185 | static $sanitized_nodes = array(); |
| 186 | ||
| 1fbe637d | 187 | switch ($op) { |
| c9eaf362 YC |
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; | |
| 14ddf71e | 207 | |
| 1fbe637d | 208 | case 'validate': |
| 14ddf71e YC |
209 | // Extract nids to check. |
| 210 | $ids = array(); | |
| 211 | foreach ($items as $delta => $item) { | |
| 91a7f662 YC |
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 | } | |
| 14ddf71e YC |
221 | } |
| 222 | } | |
| 91a7f662 YC |
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 | } | |
| 40423ad9 | 233 | } |
| 1fbe637d KS |
234 | } |
| 235 | } | |
| 9b5083a1 | 236 | return $items; |
| d6d7c227 KS |
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; | |
| 6130c745 KS |
274 | } |
| 275 | } | |
| e9b6b501 | 276 | |
| 6130c745 KS |
277 | /** |
| 278 | * Implementation of hook_content_is_empty(). | |
| 279 | */ | |
| 92041ef8 | 280 | function nodereference_content_is_empty($item, $field) { |
| 6130c745 KS |
281 | if (empty($item['nid'])) { |
| 282 | return TRUE; | |
| 1fbe637d | 283 | } |
| 6130c745 | 284 | return FALSE; |
| 1fbe637d | 285 | } |
| 1182d802 | 286 | |
| 319350d8 | 287 | /** |
| 53d5a65c | 288 | * Implementation of hook_field_formatter_info(). |
| 319350d8 | 289 | */ |
| 53d5a65c KS |
290 | function nodereference_field_formatter_info() { |
| 291 | return array( | |
| 292 | 'default' => array( | |
| d4353143 | 293 | 'label' => t('Title (link)'), |
| 53d5a65c | 294 | 'field types' => array('nodereference'), |
| 089838e7 | 295 | 'multiple values' => CONTENT_HANDLE_CORE, |
| 53d5a65c KS |
296 | ), |
| 297 | 'plain' => array( | |
| d4353143 | 298 | 'label' => t('Title (no link)'), |
| 53d5a65c | 299 | 'field types' => array('nodereference'), |
| 089838e7 | 300 | 'multiple values' => CONTENT_HANDLE_CORE, |
| 53d5a65c | 301 | ), |
| c519cdf1 | 302 | 'full' => array( |
| d4353143 | 303 | 'label' => t('Full node'), |
| c519cdf1 | 304 | 'field types' => array('nodereference'), |
| 089838e7 | 305 | 'multiple values' => CONTENT_HANDLE_CORE, |
| c519cdf1 YC |
306 | ), |
| 307 | 'teaser' => array( | |
| d4353143 | 308 | 'label' => t('Teaser'), |
| c519cdf1 | 309 | 'field types' => array('nodereference'), |
| 089838e7 | 310 | 'multiple values' => CONTENT_HANDLE_CORE, |
| c519cdf1 | 311 | ), |
| 53d5a65c KS |
312 | ); |
| 313 | } | |
| 314 | ||
| 315 | /** | |
| e74ada9a | 316 | * Theme function for 'default' nodereference field formatter. |
| 53d5a65c | 317 | */ |
| e74ada9a YC |
318 | function theme_nodereference_formatter_default($element) { |
| 319 | $output = ''; | |
| d6d7c227 KS |
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 | } | |
| e74ada9a YC |
326 | return $output; |
| 327 | } | |
| 62823fb9 | 328 | |
| e74ada9a YC |
329 | /** |
| 330 | * Theme function for 'plain' nodereference field formatter. | |
| 331 | */ | |
| 332 | function theme_nodereference_formatter_plain($element) { | |
| 333 | $output = ''; | |
| d6d7c227 KS |
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 | } | |
| e411c26d | 339 | } |
| e74ada9a YC |
340 | return $output; |
| 341 | } | |
| 53d5a65c | 342 | |
| e74ada9a YC |
343 | /** |
| 344 | * Proxy theme function for 'full' and 'teaser' nodereference field formatters. | |
| 345 | */ | |
| 346 | function theme_nodereference_formatter_full_teaser($element) { | |
| 347 | static $recursion_queue = array(); | |
| 348 | $output = ''; | |
| d6d7c227 KS |
349 | if (!empty($element['#item']['safe']['nid'])) { |
| 350 | $nid = $element['#item']['safe']['nid']; | |
| f4004b24 YC |
351 | $node = $element['#node']; |
| 352 | $field = content_fields($element['#field_name'], $element['#type_name']); | |
| 62823fb9 YC |
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; | |
| d6d7c227 | 358 | if (in_array($nid, $recursion_queue)) { |
| 3a2d35f5 | 359 | // Prevent infinite recursion caused by reference cycles: |
| 62823fb9 YC |
360 | // if the node has already been rendered earlier in this 'thread', |
| 361 | // we fall back to 'default' (node title) formatter. | |
| e74ada9a | 362 | return theme('nodereference_formatter_default', $element); |
| 62823fb9 | 363 | } |
| d6d7c227 KS |
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'); | |
| 1d2ddfd3 | 368 | } |
| c6396b9d | 369 | } |
| e74ada9a YC |
370 | return $output; |
| 371 | } | |
| c519cdf1 | 372 | |
| e74ada9a YC |
373 | /** |
| 374 | * Helper function for formatters. | |
| 375 | * | |
| 376 | * Store node titles collected in the curent request. | |
| 377 | */ | |
| 378 | function _nodereference_titles($nid, $known_title = NULL) { | |
| 379 | static $titles = array(); | |
| 380 | if (!isset($titles[$nid])) { | |
| 3cba966e | 381 | $title = $known_title ? $known_title : db_result(db_query(db_rewrite_sql("SELECT n.title FROM {node} n WHERE n.nid=%d"), $nid)); |
| e74ada9a | 382 | $titles[$nid] = $title ? $title : ''; |
| 1182d802 | 383 | } |
| e74ada9a | 384 | return $titles[$nid]; |
| 1182d802 JC |
385 | } |
| 386 | ||
| 1182d802 JC |
387 | /** |
| 388 | * Implementation of hook_widget_info(). | |
| e9b6b501 KS |
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. | |
| 1182d802 JC |
400 | */ |
| 401 | function nodereference_widget_info() { | |
| 402 | return array( | |
| 403 | 'nodereference_select' => array( | |
| 2400f882 | 404 | 'label' => t('Select list'), |
| 1182d802 | 405 | 'field types' => array('nodereference'), |
| a4418efc | 406 | 'multiple values' => CONTENT_HANDLE_MODULE, |
| e9b6b501 KS |
407 | 'callbacks' => array( |
| 408 | 'default value' => CONTENT_CALLBACK_DEFAULT, | |
| d4353143 | 409 | ), |
| 1182d802 | 410 | ), |
| 578c7b49 YC |
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 | ), | |
| 1182d802 | 419 | 'nodereference_autocomplete' => array( |
| 2400f882 | 420 | 'label' => t('Autocomplete text field'), |
| 1182d802 | 421 | 'field types' => array('nodereference'), |
| a4418efc | 422 | 'multiple values' => CONTENT_HANDLE_CORE, |
| e9b6b501 KS |
423 | 'callbacks' => array( |
| 424 | 'default value' => CONTENT_CALLBACK_DEFAULT, | |
| d4353143 | 425 | ), |
| 1182d802 JC |
426 | ), |
| 427 | ); | |
| 428 | } | |
| 429 | ||
| 430 | /** | |
| ea73d362 KS |
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 | */ | |
| 442 | function nodereference_elements() { | |
| 443 | return array( | |
| 444 | 'nodereference_select' => array( | |
| 445 | '#input' => TRUE, | |
| 446 | '#columns' => array('uid'), '#delta' => 0, | |
| 447 | '#process' => array('nodereference_select_process'), | |
| 4ae62d83 | 448 | ), |
| 578c7b49 YC |
449 | 'nodereference_buttons' => array( |
| 450 | '#input' => TRUE, | |
| 451 | '#columns' => array('uid'), '#delta' => 0, | |
| 452 | '#process' => array('nodereference_buttons_process'), | |
| 4ae62d83 | 453 | ), |
| ea73d362 KS |
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 | /** | |
| 14ddf71e YC |
464 | * Implementation of hook_widget_settings(). |
| 465 | */ | |
| 466 | function nodereference_widget_settings($op, $widget) { | |
| 467 | switch ($op) { | |
| 468 | case 'form': | |
| 469 | $form = array(); | |
| 470 | $match = isset($widget['autocomplete_match']) ? $widget['autocomplete_match'] : 'contains'; | |
| 0a6df163 | 471 | $size = (isset($widget['size']) && is_numeric($widget['size'])) ? $widget['size'] : 60; |
| 14ddf71e YC |
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 | ); | |
| 0a6df163 YC |
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 | ); | |
| 14ddf71e YC |
490 | } |
| 491 | else { | |
| 492 | $form['autocomplete_match'] = array('#type' => 'hidden', '#value' => $match); | |
| 0a6df163 | 493 | $form['size'] = array('#type' => 'hidden', '#value' => $size); |
| 14ddf71e YC |
494 | } |
| 495 | return $form; | |
| 496 | ||
| 497 | case 'save': | |
| 0a6df163 | 498 | return array('autocomplete_match', 'size'); |
| 14ddf71e YC |
499 | } |
| 500 | } | |
| 501 | ||
| 502 | /** | |
| 1182d802 | 503 | * Implementation of hook_widget(). |
| e9b6b501 KS |
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 | |
| 1182d802 | 532 | */ |
| e9b6b501 | 533 | function nodereference_widget(&$form, &$form_state, $field, $items, $delta = 0) { |
| e9b6b501 KS |
534 | switch ($field['widget']['type']) { |
| 535 | case 'nodereference_select': | |
| e9b6b501 | 536 | $element = array( |
| ea73d362 | 537 | '#type' => 'nodereference_select', |
| e9b6b501 KS |
538 | '#default_value' => $items, |
| 539 | ); | |
| 540 | break; | |
| 541 | ||
| 578c7b49 YC |
542 | case 'nodereference_buttons': |
| 543 | $element = array( | |
| 544 | '#type' => 'nodereference_buttons', | |
| 545 | '#default_value' => $items, | |
| 546 | ); | |
| 547 | break; | |
| 548 | ||
| e9b6b501 | 549 | case 'nodereference_autocomplete': |
| e9b6b501 | 550 | $element = array( |
| ea73d362 | 551 | '#type' => 'nodereference_autocomplete', |
| e9b6b501 KS |
552 | '#default_value' => isset($items[$delta]) ? $items[$delta] : NULL, |
| 553 | '#value_callback' => 'nodereference_autocomplete_value', | |
| e9b6b501 KS |
554 | ); |
| 555 | break; | |
| 556 | } | |
| 557 | return $element; | |
| 558 | } | |
| aa5d88fc | 559 | |
| e9b6b501 KS |
560 | /** |
| 561 | * Value for a nodereference autocomplete element. | |
| 562 | * | |
| 563 | * Substitute in the node title for the node nid. | |
| 564 | */ | |
| 565 | function nodereference_autocomplete_value($element, $edit = FALSE) { | |
| 77684855 KS |
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); | |
| ea73d362 | 572 | } |
| 77684855 | 573 | return array($field_key => NULL); |
| ea73d362 KS |
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 | * | |
| 32ead958 | 582 | * The $fields array is in $form['#field_info'][$element['#field_name']]. |
| ea73d362 KS |
583 | */ |
| 584 | function 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. | |
| 617d3264 | 587 | // This will create a new, nested instance of the field. |
| ea73d362 KS |
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', | |
| 443b2f04 | 592 | '#default_value' => isset($element['#value']) ? $element['#value'] : '', |
| 8c06d539 KS |
593 | // The following values were set by the content module and need |
| 594 | // to be passed down to the nested element. | |
| 8c06d539 | 595 | '#title' => $element['#title'], |
| 341f4230 | 596 | '#required' => $element['#required'], |
| 7dbb8603 | 597 | '#description' => $element['#description'], |
| 07c92176 YC |
598 | '#field_name' => $element['#field_name'], |
| 599 | '#type_name' => $element['#type_name'], | |
| 600 | '#delta' => $element['#delta'], | |
| 601 | '#columns' => $element['#columns'], | |
| ea73d362 | 602 | ); |
| 617d3264 KS |
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'); | |
| ea73d362 KS |
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 | * | |
| 578c7b49 YC |
616 | * The $fields array is in $form['#field_info'][$element['#field_name']]. |
| 617 | */ | |
| 618 | function 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. | |
| 617d3264 | 621 | // This will create a new, nested instance of the field. |
| 578c7b49 YC |
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'] : '', | |
| 578c7b49 YC |
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 | ); | |
| 617d3264 KS |
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'); | |
| 578c7b49 YC |
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 | * | |
| ea73d362 KS |
650 | */ |
| 651 | function nodereference_autocomplete_process($element, $edit, $form_state, $form) { | |
| 7e578d66 | 652 | |
| ea73d362 KS |
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]; | |
| ea73d362 | 658 | |
| ea73d362 KS |
659 | $element[$field_key] = array( |
| 660 | '#type' => 'text_textfield', | |
| 92efa608 | 661 | '#default_value' => isset($element['#value']) ? $element['#value'] : '', |
| 411876a9 | 662 | '#autocomplete_path' => 'nodereference/autocomplete/'. $element['#field_name'], |
| 8c06d539 KS |
663 | // The following values were set by the content module and need |
| 664 | // to be passed down to the nested element. | |
| 8c06d539 | 665 | '#title' => $element['#title'], |
| 341f4230 | 666 | '#required' => $element['#required'], |
| 7dbb8603 | 667 | '#description' => $element['#description'], |
| 07c92176 YC |
668 | '#field_name' => $element['#field_name'], |
| 669 | '#type_name' => $element['#type_name'], | |
| 670 | '#delta' => $element['#delta'], | |
| 671 | '#columns' => $element['#columns'], | |
| ea73d362 | 672 | ); |
| 617d3264 KS |
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'); | |
| cabf5d2a | 677 | |
| 452a390c KS |
678 | // Used so that hook_field('validate') knows where to flag an error. |
| 679 | $element['_error_element'] = array( | |
| 680 | '#type' => 'value', | |
| d1eac231 | 681 | // Wrapping the element around a text_textfield element creates a |
| 617d3264 | 682 | // nested element, so the final id will look like 'field-name-0-nid-nid'. |
| 452a390c KS |
683 | '#value' => implode('][', array_merge($element['#parents'], array($field_key, $field_key))), |
| 684 | ); | |
| ea73d362 | 685 | return $element; |
| e9b6b501 | 686 | } |
| bbbd884e | 687 | |
| ea73d362 | 688 | /** |
| 4ae62d83 | 689 | * Validate a select/buttons element. |
| ea73d362 KS |
690 | * |
| 691 | * Remove the wrapper layer and set the right element's value. | |
| d30e72da KS |
692 | * We don't know exactly where this element is, so we drill down |
| 693 | * through the element until we get to our key. | |
| d1eac231 | 694 | * |
| 617d3264 KS |
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. | |
| ea73d362 | 698 | */ |
| ecf1a4f0 | 699 | function nodereference_optionwidgets_validate($element, &$form_state) { |
| 8cc29b62 | 700 | $field_key = $element['#columns'][0]; |
| d1eac231 | 701 | |
| d30e72da | 702 | $value = $form_state['values']; |
| 617d3264 | 703 | $new_parents = array(); |
| d30e72da KS |
704 | foreach ($element['#parents'] as $parent) { |
| 705 | $value = $value[$parent]; | |
| a6846447 KS |
706 | // Use === to be sure we get right results if parent is a zero (delta) value. |
| 707 | if ($parent === $field_key) { | |
| d30e72da | 708 | $element['#parents'] = $new_parents; |
| 3a2d35f5 | 709 | form_set_value($element, $value, $form_state); |
| d30e72da KS |
710 | break; |
| 711 | } | |
| 712 | $new_parents[] = $parent; | |
| 713 | } | |
| ea73d362 KS |
714 | } |
| 715 | ||
| 716 | /** | |
| 717 | * Validate an autocomplete element. | |
| 718 | * | |
| 719 | * Remove the wrapper layer and set the right element's value. | |
| d1eac231 | 720 | * This will move the nested value at 'field-name-0-nid-nid' |
| 617d3264 | 721 | * back to its original location, 'field-name-0-nid'. |
| ea73d362 KS |
722 | */ |
| 723 | function nodereference_autocomplete_validate($element, &$form_state) { | |
| 5ce201bf | 724 | $field_name = $element['#field_name']; |
| 07c92176 YC |
725 | $type_name = $element['#type_name']; |
| 726 | $field = content_fields($field_name, $type_name); | |
| e9b6b501 | 727 | $field_key = $element['#columns'][0]; |
| 8588bda8 | 728 | $delta = $element['#delta']; |
| e9b6b501 | 729 | $value = $element['#value'][$field_key]; |
| b903d4d0 | 730 | $nid = NULL; |
| e9b6b501 KS |
731 | if (!empty($value)) { |
| 732 | preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $value, $matches); | |
| 733 | if (!empty($matches)) { | |
| 7e578d66 | 734 | // Explicit [nid:n]. |
| e9b6b501 | 735 | list(, $title, $nid) = $matches; |
| ab5599ef | 736 | if (!empty($title) && ($n = node_load($nid)) && trim($title) != trim($n->title)) { |
| d6166089 | 737 | form_error($element[$field_key], t('%name: title mismatch. Please check your selection.', array('%name' => t($field['widget']['label'])))); |
| e9b6b501 KS |
738 | } |
| 739 | } | |
| 740 | else { | |
| 7e578d66 | 741 | // No explicit nid. |
| 14ddf71e YC |
742 | $reference = _nodereference_potential_references($field, $value, 'equals', NULL, 1); |
| 743 | if (empty($reference)) { | |
| d6166089 | 744 | form_error($element[$field_key], t('%name: found no valid post with that title.', array('%name' => t($field['widget']['label'])))); |
| 58330219 YC |
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... | |
| 14ddf71e | 751 | $nid = key($reference); |
| 58330219 | 752 | } |
| e9b6b501 | 753 | } |
| e1a2f8a6 | 754 | } |
| b903d4d0 | 755 | form_set_value($element, $nid, $form_state); |
| e9b6b501 KS |
756 | } |
| 757 | ||
| 758 | /** | |
| 759 | * Implementation of hook_allowed_values(). | |
| 760 | */ | |
| 761 | function nodereference_allowed_values($field) { | |
| 7e578d66 YC |
762 | $references = _nodereference_potential_references($field); |
| 763 | ||
| 764 | $options = array(); | |
| 765 | foreach ($references as $key => $value) { | |
| 948f3ff9 | 766 | $options[$key] = $value['rendered']; |
| e9b6b501 | 767 | } |
| d0c6d4fe | 768 | |
| e9b6b501 | 769 | return $options; |
| e1a2f8a6 JC |
770 | } |
| 771 | ||
| 772 | /** | |
| 7e578d66 YC |
773 | * Fetch an array of all candidate referenced nodes. |
| 774 | * | |
| 8ab268df | 775 | * This info is used in various places (allowed values, autocomplete results, |
| 7e578d66 YC |
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 | |
| 14ddf71e YC |
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. | |
| 7e578d66 YC |
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 | * ) | |
| e1a2f8a6 | 803 | */ |
| 14ddf71e | 804 | function _nodereference_potential_references($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { |
| 7e578d66 | 805 | static $results = array(); |
| e1a2f8a6 | 806 | |
| 14ddf71e YC |
807 | // Create unique id for static cache. |
| 808 | $cid = $field['field_name'] .':'. $match .':'. ($string !== '' ? $string : implode('-', $ids)) .':'. $limit; | |
| 809 | if (!isset($results[$cid])) { | |
| 7e578d66 YC |
810 | $references = FALSE; |
| 811 | if (module_exists('views') && !empty($field['advanced_view']) && $field['advanced_view'] != '--') { | |
| 14ddf71e | 812 | $references = _nodereference_potential_references_views($field, $string, $match, $ids, $limit); |
| 0c2959db | 813 | } |
| 7e578d66 | 814 | // If the view doesn't exist, we got FALSE, and fallback to the regular 'standard mode'. |
| 0c2959db | 815 | |
| 7e578d66 | 816 | if ($references === FALSE) { |
| 14ddf71e | 817 | $references = _nodereference_potential_references_standard($field, $string, $match, $ids, $limit); |
| 0c2959db KS |
818 | } |
| 819 | ||
| 7e578d66 | 820 | // Store the results. |
| 0311a4d5 | 821 | $results[$cid] = !empty($references) ? $references : array(); |
| 63e0b10d | 822 | } |
| e1a2f8a6 | 823 | |
| 14ddf71e | 824 | return $results[$cid]; |
| e1a2f8a6 | 825 | } |
| 0f6ca9bb | 826 | |
| 0f6ca9bb | 827 | /** |
| 7e578d66 YC |
828 | * Helper function for _nodereference_potential_references(): |
| 829 | * case of Views-defined referenceable nodes. | |
| 328c1ca6 | 830 | */ |
| 14ddf71e | 831 | function _nodereference_potential_references_views($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { |
| 328c1ca6 | 832 | $view_name = $field['advanced_view']; |
| 7e578d66 YC |
833 | |
| 834 | if ($view = views_get_view($view_name)) { | |
| c27126a5 YC |
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... | |
| 14ddf71e | 837 | $display = $view->add_display('content_references'); |
| 7e578d66 YC |
838 | $view->set_display($display); |
| 839 | ||
| 7e578d66 YC |
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. | |
| d49aafb0 | 842 | $view->display_handler->set_option('style_plugin', 'content_php_array_autocomplete'); |
| 7e578d66 | 843 | $view->display_handler->set_option('row_plugin', 'fields'); |
| d49aafb0 YC |
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'); | |
| 3a2d35f5 | 847 | |
| 14ddf71e YC |
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. | |
| 7bb93942 YC |
864 | $limit = isset($limit) ? $limit : 0; |
| 865 | $view->display_handler->set_option('items_per_page', $limit); | |
| 14ddf71e | 866 | |
| 5cb7f057 | 867 | // Get arguments for the view. |
| e1655be5 | 868 | if (!empty($field['advanced_view_args'])) { |
| 328c1ca6 | 869 | // TODO: Support Tokens using token.module ? |
| 328c1ca6 | 870 | $view_args = array_map('trim', explode(',', $field['advanced_view_args'])); |
| 5cb7f057 KS |
871 | } |
| 872 | else { | |
| 873 | $view_args = array(); | |
| 328c1ca6 KS |
874 | } |
| 875 | ||
| 7e578d66 | 876 | // We do need title field, so add it if not present (unlikely, but...) |
| c27126a5 YC |
877 | $fields = $view->get_items('field', $display); |
| 878 | if (!isset($fields['title'])) { | |
| 5cb7f057 KS |
879 | $view->add_item($display, 'field', 'node', 'title'); |
| 880 | } | |
| 328c1ca6 | 881 | |
| c27126a5 YC |
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); | |
| 7e578d66 | 891 | |
| 328c1ca6 KS |
892 | // Make sure the query is not cached |
| 893 | $view->is_cacheable = FALSE; | |
| 7e578d66 YC |
894 | |
| 895 | // Get the results. | |
| 896 | $result = $view->execute_display($display, $view_args); | |
| 897 | } | |
| 898 | else { | |
| 899 | $result = FALSE; | |
| 328c1ca6 | 900 | } |
| 3a2d35f5 | 901 | |
| 7e578d66 | 902 | return $result; |
| 328c1ca6 KS |
903 | } |
| 904 | ||
| 905 | /** | |
| 7e578d66 YC |
906 | * Helper function for _nodereference_potential_references(): |
| 907 | * referenceable nodes defined by content types. | |
| 0f6ca9bb | 908 | */ |
| 14ddf71e | 909 | function _nodereference_potential_references_standard($field, $string = '', $match = 'contains', $ids = array(), $limit = NULL) { |
| 7e578d66 | 910 | $related_types = array(); |
| 14ddf71e | 911 | $where = array(); |
| 7e578d66 YC |
912 | $args = array(); |
| 913 | ||
| 914 | if (is_array($field['referenceable_types'])) { | |
| 915 | foreach (array_filter($field['referenceable_types']) as $related_type) { | |
| 14ddf71e | 916 | $related_types[] = "n.type = '%s'"; |
| 7e578d66 YC |
917 | $args[] = $related_type; |
| 918 | } | |
| 0f6ca9bb | 919 | } |
| 53d5a65c | 920 | |
| 14ddf71e | 921 | $where[] = implode(' OR ', $related_types); |
| 7e578d66 YC |
922 | |
| 923 | if (!count($related_types)) { | |
| 924 | return array(); | |
| 63e0b10d | 925 | } |
| 7e578d66 | 926 | |
| 069484bb | 927 | if ($string !== '') { |
| 121f60a6 KS |
928 | $like = $GLOBALS["db_type"] == 'pgsql' ? "ILIKE" : "LIKE"; |
| 929 | $match_clauses = array( | |
| 930 | 'contains' => "$like '%%%s%%'", | |
| 14ddf71e | 931 | 'equals' => "= '%s'", |
| 121f60a6 | 932 | 'starts_with' => "$like '%s%%'", |
| 14ddf71e | 933 | ); |
| 6dd9b4e4 | 934 | $where[] = 'n.title '. (isset($match_clauses[$match]) ? $match_clauses[$match] : $match_clauses['contains']); |
| 7e578d66 | 935 | $args[] = $string; |
| 63e0b10d | 936 | } |
| 14ddf71e YC |
937 | elseif ($ids) { |
| 938 | $where[] = 'n.nid IN (' . db_placeholders($ids) . ')'; | |
| 939 | $args = array_merge($args, $ids); | |
| 940 | } | |
| 0c2959db | 941 | |
| 14ddf71e YC |
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); | |
| 7e578d66 YC |
945 | $references = array(); |
| 946 | while ($node = db_fetch_object($result)) { | |
| 947 | $references[$node->nid] = array( | |
| 948 | 'title' => $node->node_title, | |
| 948f3ff9 | 949 | 'rendered' => check_plain($node->node_title), |
| 7e578d66 | 950 | ); |
| 0c2959db | 951 | } |
| 7e578d66 YC |
952 | |
| 953 | return $references; | |
| 0c2959db KS |
954 | } |
| 955 | ||
| d6d7c227 KS |
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 | */ | |
| 962 | function nodereference_autocomplete_access($field_name) { | |
| 898a7b60 | 963 | return user_access('access content') && ($field = content_fields($field_name)) && isset($field['field_name']) && content_access('view', $field) && content_access('edit', $field); |
| d6d7c227 KS |
964 | } |
| 965 | ||
| 7e578d66 | 966 | /** |
| 4ae62d83 | 967 | * Menu callback; Retrieve a pipe delimited string of autocomplete suggestions for existing users |
| 7e578d66 YC |
968 | */ |
| 969 | function nodereference_autocomplete($field_name, $string = '') { | |
| 970 | $fields = content_fields(); | |
| 971 | $field = $fields[$field_name]; | |
| 14ddf71e | 972 | $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains'; |
| 7e578d66 YC |
973 | $matches = array(); |
| 974 | ||
| 14ddf71e | 975 | $references = _nodereference_potential_references($field, $string, $match, array(), 10); |
| 7e578d66 YC |
976 | foreach ($references as $id => $row) { |
| 977 | // Add a class wrapper for a few required CSS overrides. | |
| 2a1f05cb | 978 | $matches[$row['title'] ." [nid:$id]"] = '<div class="reference-autocomplete">'. $row['rendered'] . '</div>'; |
| 7e578d66 YC |
979 | } |
| 980 | drupal_json($matches); | |
| 0c2959db KS |
981 | } |
| 982 | ||
| 53d5a65c | 983 | /** |
| 4ae62d83 YC |
984 | * Implementation of hook_node_types. |
| 985 | */ | |
| 986 | function 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(); | |
| ffb0541f | 994 | $rebuild = FALSE; |
| 4ae62d83 YC |
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]); | |
| ffb0541f MFM |
999 | content_field_instance_update($field, FALSE); |
| 1000 | $rebuild = TRUE; | |
| 4ae62d83 YC |
1001 | } |
| 1002 | } | |
| ffb0541f MFM |
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 | } | |
| 4ae62d83 YC |
1009 | } |
| 1010 | break; | |
| 1011 | } | |
| 1012 | } | |
| 1013 | ||
| 47c53f3b YC |
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 | */ | |
| 1020 | function 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 | } | |
| 4ae62d83 YC |
1032 | |
| 1033 | /** | |
| ea73d362 KS |
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 | */ | |
| 1044 | function theme_nodereference_select($element) { | |
| 1045 | return $element['#children']; | |
| 1046 | } | |
| 1047 | ||
| 578c7b49 YC |
1048 | function theme_nodereference_buttons($element) { |
| 1049 | return $element['#children']; | |
| 1050 | } | |
| 1051 | ||
| ea73d362 KS |
1052 | function theme_nodereference_autocomplete($element) { |
| 1053 | return $element['#children']; | |
| c9eaf362 | 1054 | } |