node_reference.module 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195
  1. <?php
  2. /**
  3. * @file
  4. * Defines a field type for referencing one node from another.
  5. */
  6. /**
  7. * Implements hook_menu().
  8. */
  9. function node_reference_menu() {
  10. $items['node_reference/autocomplete/%/%/%'] = array(
  11. 'page callback' => 'node_reference_autocomplete',
  12. 'page arguments' => array(2, 3, 4),
  13. 'access callback' => 'reference_autocomplete_access',
  14. 'access arguments' => array(2, 3, 4),
  15. 'type' => MENU_CALLBACK,
  16. );
  17. return $items;
  18. }
  19. /**
  20. * Implements hook_field_info().
  21. */
  22. function node_reference_field_info() {
  23. return array(
  24. 'node_reference' => array(
  25. 'label' => t('Node reference'),
  26. 'description' => t('This field stores the ID of a related node as an integer value.'),
  27. 'settings' => array(
  28. 'referenceable_types' => array(),
  29. 'view' => array(
  30. 'view_name' => '',
  31. 'display_name' => '',
  32. 'args' => array(),
  33. ),
  34. ),
  35. // It probably make more sense to have the referenceable types be per-field than per-instance
  36. // 'instance settings' => array('referenceable_types' => array()),
  37. 'default_widget' => 'options_select', // node_reference_autocomplete',
  38. 'default_formatter' => 'node_reference_default',
  39. // Support hook_entity_property_info() from contrib "Entity API".
  40. 'property_type' => 'node',
  41. // Support default token formatter for field tokens.
  42. 'default_token_formatter' => 'node_reference_plain',
  43. ),
  44. );
  45. }
  46. /**
  47. * Implements hook_field_schema().
  48. */
  49. function node_reference_field_schema($field) {
  50. $columns = array(
  51. 'nid' => array(
  52. 'type' => 'int',
  53. 'unsigned' => TRUE,
  54. 'not null' => FALSE,
  55. ),
  56. );
  57. return array(
  58. 'columns' => $columns,
  59. 'indexes' => array('nid' => array('nid')),
  60. 'foreign keys' => array(
  61. 'nid' => array(
  62. 'table' => 'node',
  63. 'columns' => array('nid' => 'nid'),
  64. ),
  65. ),
  66. );
  67. }
  68. /**
  69. * Implements hook_field_settings_form().
  70. */
  71. function node_reference_field_settings_form($field, $instance, $has_data) {
  72. $settings = $field['settings'];
  73. $form = array();
  74. $form['referenceable_types'] = array(
  75. '#type' => 'checkboxes',
  76. '#title' => t('Content types that can be referenced'),
  77. '#multiple' => TRUE,
  78. '#default_value' => $settings['referenceable_types'],
  79. '#options' => array_map('check_plain', node_type_get_names()),
  80. );
  81. if (module_exists('views')) {
  82. $view_settings = $settings['view'];
  83. $description = '<p>' . t('The list of nodes that can be referenced can provided by a view (Views module) using the "References" display type.') . '</p>';
  84. // Special note for legacy fields migrated from D6.
  85. if (!empty($view_settings['view_name']) && $view_settings['display_name'] == 'default') {
  86. $description .= '<p><strong><span class="admin-missing">'. t("Important D6 migration note:") . '</span></strong>';
  87. $description .= '<br/>' . t("The field is currently configured to use the 'Master' display of the view %view_name.", array('%view_name' => $view_settings['view_name']));
  88. $description .= '<br/>' . t("It is highly recommended that you: <br/>- edit this view and create a new display using the 'References' display type, <br/>- update the field settings to explicitly select the correct view and display.");
  89. $description .= '<br/>' . t("The field will work correctly until then, but submitting this form might inadvertently change the field settings.") . '</p>';
  90. }
  91. $form['view'] = array(
  92. '#type' => 'fieldset',
  93. '#title' => t('Views - Nodes that can be referenced'),
  94. '#collapsible' => TRUE,
  95. '#collapsed' => empty($view_settings['view_name']),
  96. '#description' => $description,
  97. );
  98. $views_options = references_get_views_options('node');
  99. if ($views_options) {
  100. // The value of the 'view_and_display' select below will need to be split
  101. // into 'view_name' and 'view_display' in the final submitted values, so
  102. // we massage the data at validate time on the wrapping element (not
  103. // ideal).
  104. $form['view']['#element_validate'] = array('_node_reference_view_settings_validate');
  105. $views_options = array('' => '<' . t('none') . '>') + $views_options;
  106. $default = empty($view_settings['view_name']) ? '' : $view_settings['view_name'] . ':' .$view_settings['display_name'];
  107. $form['view']['view_and_display'] = array(
  108. '#type' => 'select',
  109. '#title' => t('View used to select the nodes'),
  110. '#options' => $views_options,
  111. '#default_value' => $default,
  112. '#description' => '<p>' . t('Choose the view and display that select the nodes that can be referenced.<br />Only views with a display of type "References" are eligible.') . '</p>' .
  113. t('Note:<ul><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>'),
  114. );
  115. $default = implode(', ', $view_settings['args']);
  116. $form['view']['args'] = array(
  117. '#type' => 'textfield',
  118. '#title' => t('View arguments'),
  119. '#default_value' => $default,
  120. '#required' => FALSE,
  121. '#description' => t('Provide a comma separated list of arguments to pass to the view.'),
  122. );
  123. }
  124. else {
  125. $form['view']['no_view_help'] = array(
  126. '#markup' => '<p>' . t('No eligible view was found.') .'</p>',
  127. );
  128. }
  129. }
  130. return $form;
  131. }
  132. /**
  133. * Validate callback for the 'view settings' fieldset.
  134. *
  135. * Puts back the various form values in the expected shape.
  136. */
  137. function _node_reference_view_settings_validate($element, &$form_state, $form) {
  138. // Split view name and display name from the 'view_and_display' value.
  139. if (!empty($element['view_and_display']['#value'])) {
  140. list($view, $display) = explode(':', $element['view_and_display']['#value']);
  141. }
  142. else {
  143. $view = '';
  144. $display = '';
  145. }
  146. // Explode the 'args' string into an actual array. Beware, explode() turns an
  147. // empty string into an array with one empty string. We'll need an empty array
  148. // instead.
  149. $args_string = trim($element['args']['#value']);
  150. $args = ($args_string === '') ? array() : array_map('trim', explode(',', $args_string));
  151. $value = array('view_name' => $view, 'display_name' => $display, 'args' => $args);
  152. form_set_value($element, $value, $form_state);
  153. }
  154. /**
  155. * Implements hook_field_validate().
  156. *
  157. * Possible error codes:
  158. * - 'invalid_nid': nid is not valid for the field (not a valid node id, or the node is not referenceable).
  159. */
  160. function node_reference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
  161. // Extract nids to check.
  162. $ids = array();
  163. // First check non-numeric "nid's to avoid losing time with them.
  164. foreach ($items as $delta => $item) {
  165. if (is_array($item) && !empty($item['nid'])) {
  166. if (is_numeric($item['nid'])) {
  167. $ids[] = $item['nid'];
  168. }
  169. else {
  170. $errors[$field['field_name']][$langcode][$delta][] = array(
  171. 'error' => 'invalid_nid',
  172. 'message' => t("%name: invalid input.",
  173. array('%name' => $instance['label'])),
  174. );
  175. }
  176. }
  177. }
  178. // Prevent performance hog if there are no ids to check.
  179. if ($ids) {
  180. $options = array(
  181. 'ids' => $ids,
  182. );
  183. $refs = node_reference_potential_references($field, $options);
  184. foreach ($items as $delta => $item) {
  185. if (is_array($item)) {
  186. if (!empty($item['nid']) && !isset($refs[$item['nid']])) {
  187. $errors[$field['field_name']][$langcode][$delta][] = array(
  188. 'error' => 'invalid_nid',
  189. 'message' => t("%name: this post can't be referenced.",
  190. array('%name' => $instance['label'])),
  191. );
  192. }
  193. }
  194. }
  195. }
  196. }
  197. /**
  198. * Implements hook_field_prepare_view().
  199. */
  200. function node_reference_field_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items) {
  201. $checked_ids = &drupal_static(__FUNCTION__, array());
  202. // Set an 'access' property on each item (TRUE if the node exists and is
  203. // accessible by the current user.
  204. // Extract ids to check.
  205. $ids = array();
  206. foreach ($items as $id => $entity_items) {
  207. foreach ($entity_items as $delta => $item) {
  208. if (is_array($item)) {
  209. // Default to 'not accessible'.
  210. $items[$id][$delta]['access'] = FALSE;
  211. if (!empty($item['nid']) && is_numeric($item['nid'])) {
  212. $ids[$item['nid']] = $item['nid'];
  213. }
  214. }
  215. }
  216. }
  217. if ($ids) {
  218. // Load information about ids that we haven't already loaded during this
  219. // page request.
  220. $ids_to_check = array_diff($ids, array_keys($checked_ids));
  221. if (!empty($ids_to_check)) {
  222. $query = db_select('node', 'n')
  223. ->fields('n', array('nid'))
  224. ->addTag('node_access');
  225. $condition = db_and()
  226. ->condition('n.nid', $ids_to_check, 'IN')
  227. ->condition('n.status', NODE_PUBLISHED);
  228. // Take the 'view own unpublished content' permission into account to
  229. // decide whether unpublished nodes should be hidden.
  230. if (!user_access('administer nodes') && !user_access('bypass node access')) {
  231. if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT nid FROM {node} WHERE uid = :uid AND status = :status', array(':uid' => $GLOBALS['user']->uid, ':status' => NODE_NOT_PUBLISHED))->fetchCol()) {
  232. // (n.nid IN (nodes) AND n.status = 1) OR n.nid = (unpublished)
  233. $condition = db_or()
  234. ->condition($condition)
  235. ->condition('n.nid', $own_unpublished, 'IN');
  236. }
  237. }
  238. $query->condition($condition);
  239. $accessible_ids = $query->execute()->fetchAllAssoc('nid');
  240. // Populate our static list so that we do not query on those ids again.
  241. foreach ($ids_to_check as $id) {
  242. $checked_ids[$id] = isset($accessible_ids[$id]);
  243. }
  244. }
  245. foreach ($items as $id => $entity_items) {
  246. foreach ($entity_items as $delta => $item) {
  247. if (is_array($item) && !empty($item['nid']) && !empty($checked_ids[$item['nid']])) {
  248. $items[$id][$delta]['access'] = TRUE;
  249. }
  250. }
  251. }
  252. }
  253. }
  254. /**
  255. * Implements hook_field_is_empty().
  256. */
  257. function node_reference_field_is_empty($item, $field) {
  258. // nid = 0 is empty too, which is exactly what we want.
  259. return empty($item['nid']);
  260. }
  261. /**
  262. * Implements hook_field_formatter_info().
  263. */
  264. function node_reference_field_formatter_info() {
  265. $ret = array(
  266. 'node_reference_default' => array(
  267. 'label' => t('Title (link)'),
  268. 'description' => t('Display the title of the referenced node as a link to the node page.'),
  269. 'field types' => array('node_reference'),
  270. ),
  271. 'node_reference_plain' => array(
  272. 'label' => t('Title (no link)'),
  273. 'description' => t('Display the title of the referenced node as plain text.'),
  274. 'field types' => array('node_reference'),
  275. ),
  276. 'node_reference_node' => array(
  277. 'label' => t('Rendered node'),
  278. 'description' => t('Display the referenced node in a specific view mode'),
  279. 'field types' => array('node_reference'),
  280. 'settings' => array('node_reference_view_mode' => 'full'),
  281. ),
  282. 'node_reference_nid' => array(
  283. 'label' => t('Node ID'),
  284. 'description' => t('Display the referenced node ID'),
  285. 'field types' => array('node_reference'),
  286. ),
  287. 'node_reference_path' => array(
  288. 'label' => t('URL as plain text'),
  289. 'description' => t('Display the URL of the referenced node'),
  290. 'field types' => array('node_reference'),
  291. 'settings' => array(
  292. 'alias' => TRUE,
  293. 'absolute' => FALSE
  294. ),
  295. ),
  296. );
  297. return $ret;
  298. }
  299. /**
  300. * Implements hook_field_formatter_settings_form().
  301. */
  302. function node_reference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  303. $display = $instance['display'][$view_mode];
  304. $settings = $display['settings'];
  305. $element = array();
  306. switch ($display['type']) {
  307. case 'node_reference_node':
  308. $entity_info = entity_get_info('node');
  309. $modes = $entity_info['view modes'];
  310. $options = array();
  311. foreach ($modes as $name => $mode) {
  312. $options[$name] = $mode['label'];
  313. }
  314. $element['node_reference_view_mode'] = array(
  315. '#title' => t('View mode'),
  316. '#type' => 'select',
  317. '#options' => $options,
  318. '#default_value' => $settings['node_reference_view_mode'],
  319. // Never empty, so no #empty_option
  320. );
  321. break;
  322. case 'node_reference_path':
  323. $element['alias'] = array(
  324. '#type' => 'checkbox',
  325. '#title' => t('Display the aliased path (if exists) instead of the system path'),
  326. '#default_value' => $settings['alias'],
  327. );
  328. $element['absolute'] = array(
  329. '#type' => 'checkbox',
  330. '#title' => t('Display an absolute URL'),
  331. '#default_value' => $settings['absolute'],
  332. );
  333. break;
  334. }
  335. return $element;
  336. }
  337. /**
  338. * Implements hook_field_formatter_settings_summary().
  339. */
  340. function node_reference_field_formatter_settings_summary($field, $instance, $view_mode) {
  341. $display = $instance['display'][$view_mode];
  342. $settings = $display['settings'];
  343. $summary = array();
  344. switch ($display['type']) {
  345. case 'node_reference_node':
  346. $entity_info = entity_get_info('node');
  347. $modes = $entity_info['view modes'];
  348. $mode = $modes[$settings['node_reference_view_mode']]['label'];
  349. $summary[] = t('View mode: %mode', array('%mode' => $mode));
  350. break;
  351. case 'node_reference_path':
  352. $summary[] = t('Aliased path: %yes_no', array('%yes_no' => $settings['alias'] ? t('Yes') : t('No')));
  353. $summary[] = t('Absolute URL: %yes_no', array('%yes_no' => $settings['absolute'] ? t('Yes') : t('No')));
  354. break;
  355. }
  356. return implode('<br />', $summary);
  357. }
  358. /**
  359. * Implements hook_field_formatter_prepare_view().
  360. *
  361. * Preload all nodes referenced by items using 'full entity' formatters.
  362. */
  363. function node_reference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
  364. // Load the referenced nodes, except for the 'node_reference_nid' which does
  365. // not need full objects.
  366. // Collect ids to load.
  367. $ids = array();
  368. foreach ($displays as $id => $display) {
  369. if ($display['type'] != 'node_reference_nid') {
  370. foreach ($items[$id] as $delta => $item) {
  371. if ($item['access']) {
  372. $ids[$item['nid']] = $item['nid'];
  373. }
  374. }
  375. }
  376. }
  377. $entities = node_load_multiple($ids);
  378. // Add the loaded nodes to the items.
  379. foreach ($displays as $id => $display) {
  380. if ($display['type'] != 'node_reference_nid') {
  381. foreach ($items[$id] as $delta => $item) {
  382. if ($item['access']) {
  383. $items[$id][$delta]['node'] = $entities[$item['nid']];
  384. }
  385. }
  386. }
  387. }
  388. }
  389. /**
  390. * Implements hook_field_formatter_view().
  391. */
  392. function node_reference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  393. $settings = $display['settings'];
  394. $result = array();
  395. switch ($display['type']) {
  396. case 'node_reference_default':
  397. case 'node_reference_plain':
  398. foreach ($items as $delta => $item) {
  399. if ($item['access']) {
  400. $node = $item['node'];
  401. $label = entity_label('node', $node);
  402. if ($display['type'] == 'node_reference_default') {
  403. $uri = entity_uri('node', $node);
  404. $result[$delta] = array(
  405. '#type' => 'link',
  406. '#title' => $label,
  407. '#href' => $uri['path'],
  408. '#options' => $uri['options'],
  409. );
  410. }
  411. else {
  412. $result[$delta] = array(
  413. '#markup' => check_plain($label),
  414. );
  415. }
  416. if (!$node->status) {
  417. $result[$delta]['#prefix'] = '<span class="node-unpublished">';
  418. $result[$delta]['#suffix'] = '</span>';
  419. }
  420. }
  421. }
  422. break;
  423. case 'node_reference_node':
  424. // To prevent infinite recursion caused by reference cycles, we store
  425. // diplayed nodes in a recursion queue.
  426. $recursion_queue = &drupal_static(__FUNCTION__, array());
  427. // If no 'referencing entity' is set, we are starting a new 'reference
  428. // thread' and need to reset the queue.
  429. // @todo Bug: $entity->referencing_entity on nodes referenced in a different
  430. // thread on the page. E.g: 1 references 1+2 / 2 references 1+2 / visit homepage.
  431. // We'd need a more accurate way...
  432. if (!isset($entity->referencing_entity)) {
  433. $recursion_queue = array();
  434. }
  435. // The recursion queue only needs to track nodes.
  436. if ($entity_type == 'node') {
  437. list($id) = entity_extract_ids($entity_type, $entity);
  438. $recursion_queue[$id] = $id;
  439. }
  440. // Check the recursion queue to determine which nodes should be fully
  441. // displayed, and which nodes will only be displayed as a title.
  442. $nodes_display = array();
  443. foreach ($items as $delta => $item) {
  444. if ($item['access'] && !isset($recursion_queue[$item['nid']])) {
  445. $nodes_display[$item['nid']] = $item['node'];
  446. }
  447. }
  448. // Load and build the fully displayed nodes.
  449. if ($nodes_display) {
  450. foreach ($nodes_display as $nid => $node) {
  451. $nodes_display[$nid]->referencing_entity = $entity;
  452. $nodes_display[$nid]->referencing_field = $field['field_name'];
  453. }
  454. $nodes_built = node_view_multiple($nodes_display, $settings['node_reference_view_mode']);
  455. }
  456. // Assemble the render array.
  457. foreach ($items as $delta => $item) {
  458. if ($item['access']) {
  459. if (isset($nodes_display[$item['nid']])) {
  460. $result[$delta] = $nodes_built['nodes'][$item['nid']];
  461. }
  462. else {
  463. $node = $item['node'];
  464. $label = entity_label('node', $node);
  465. $uri = entity_uri('node', $node);
  466. $result[$delta] = array(
  467. '#type' => 'link',
  468. '#title' => $label,
  469. '#href' => $uri['path'],
  470. '#options' => $uri['options'],
  471. );
  472. if (!$node->status) {
  473. $result[$delta]['#prefix'] = '<span class="node-unpublished">';
  474. $result[$delta]['#suffix'] = '</span>';
  475. }
  476. }
  477. }
  478. }
  479. break;
  480. case 'node_reference_nid':
  481. foreach ($items as $delta => $item) {
  482. if ($item['access']) {
  483. $result[$delta] = array(
  484. '#markup' => $item['nid'],
  485. );
  486. }
  487. }
  488. break;
  489. case 'node_reference_path':
  490. foreach ($items as $delta => $item) {
  491. if ($item['access']) {
  492. $uri = entity_uri('node', $item['node']);
  493. $options = array(
  494. 'absolute' => $settings['absolute'],
  495. 'alias' => !$settings['alias'],
  496. );
  497. $options += $uri['options'];
  498. $result[$delta] = array(
  499. '#markup' => url($uri['path'], $options),
  500. );
  501. }
  502. }
  503. break;
  504. }
  505. return $result;
  506. }
  507. /**
  508. * Implements hook_field_widget_info().
  509. */
  510. function node_reference_field_widget_info() {
  511. return array(
  512. 'node_reference_autocomplete' => array(
  513. 'label' => t('Autocomplete text field'),
  514. 'description' => t('Display the list of referenceable nodes as a textfield with autocomplete behaviour.'),
  515. 'field types' => array('node_reference'),
  516. 'settings' => array(
  517. 'autocomplete_match' => 'contains',
  518. 'size' => 60,
  519. 'autocomplete_path' => 'node_reference/autocomplete',
  520. ),
  521. ),
  522. );
  523. }
  524. /**
  525. * Implements hook_field_widget_info_alter().
  526. */
  527. function node_reference_field_widget_info_alter(&$info) {
  528. $info['options_select']['field types'][] = 'node_reference';
  529. $info['options_buttons']['field types'][] = 'node_reference';
  530. }
  531. /**
  532. * Implements hook_field_widget_settings_form().
  533. */
  534. function node_reference_field_widget_settings_form($field, $instance) {
  535. $widget = $instance['widget'];
  536. $defaults = field_info_widget_settings($widget['type']);
  537. $settings = array_merge($defaults, $widget['settings']);
  538. $form = array();
  539. if ($widget['type'] == 'node_reference_autocomplete') {
  540. $form['autocomplete_match'] = array(
  541. '#type' => 'select',
  542. '#title' => t('Autocomplete matching'),
  543. '#default_value' => $settings['autocomplete_match'],
  544. '#options' => array(
  545. 'starts_with' => t('Starts with'),
  546. 'contains' => t('Contains'),
  547. ),
  548. '#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.'),
  549. );
  550. $form['size'] = array(
  551. '#type' => 'textfield',
  552. '#title' => t('Size of textfield'),
  553. '#default_value' => $settings['size'],
  554. '#element_validate' => array('_element_validate_integer_positive'),
  555. '#required' => TRUE,
  556. );
  557. }
  558. return $form;
  559. }
  560. /**
  561. * Implements hook_field_widget_form().
  562. */
  563. function node_reference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
  564. switch ($instance['widget']['type']) {
  565. case 'node_reference_autocomplete':
  566. $element += array(
  567. '#type' => 'textfield',
  568. '#default_value' => isset($items[$delta]['nid']) ? $items[$delta]['nid'] : NULL,
  569. '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/' . $field['field_name'],
  570. '#size' => $instance['widget']['settings']['size'],
  571. '#element_validate' => array('node_reference_autocomplete_validate'),
  572. '#value_callback' => 'node_reference_autocomplete_value',
  573. );
  574. break;
  575. }
  576. return array('nid' => $element);
  577. }
  578. /**
  579. * Value callback for a node_reference autocomplete element.
  580. *
  581. * Replace the node nid with a node title.
  582. */
  583. function node_reference_autocomplete_value($element, $input = FALSE, $form_state) {
  584. if ($input === FALSE) {
  585. // We're building the displayed 'default value': expand the raw nid into
  586. // "node title [nid:n]".
  587. $nid = $element['#default_value'];
  588. if (!empty($nid)) {
  589. $q = db_select('node', 'n');
  590. $node_title_alias = $q->addField('n', 'title');
  591. $q->addTag('node_access')
  592. ->condition('n.nid', $nid)
  593. ->range(0, 1);
  594. $result = $q->execute();
  595. // @todo If no result (node doesn't exist or no access).
  596. $value = $result->fetchField();
  597. $value .= ' [nid:' . $nid . ']';
  598. return $value;
  599. }
  600. }
  601. }
  602. /**
  603. * Validation callback for a node_reference autocomplete element.
  604. */
  605. function node_reference_autocomplete_validate($element, &$form_state, $form) {
  606. $field = field_widget_field($element, $form_state);
  607. $instance = field_widget_instance($element, $form_state);
  608. $value = $element['#value'];
  609. $nid = NULL;
  610. if (!empty($value)) {
  611. // Check whether we have an explicit "[nid:n]" input.
  612. preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $value, $matches);
  613. if (!empty($matches)) {
  614. // Explicit nid. Check that the 'title' part matches the actual title for
  615. // the nid.
  616. list(, $title, $nid) = $matches;
  617. if (!empty($title)) {
  618. $real_title = db_select('node', 'n')
  619. ->fields('n', array('title'))
  620. ->condition('n.nid', $nid)
  621. ->execute()
  622. ->fetchField();
  623. if (trim($title) != trim($real_title)) {
  624. form_error($element, t('%name: title mismatch. Please check your selection.', array('%name' => $instance['label'])));
  625. }
  626. }
  627. }
  628. else {
  629. // No explicit nid (the submitted value was not populated by autocomplete
  630. // selection). Get the nid of a referencable node from the entered title.
  631. $options = array(
  632. 'string' => $value,
  633. 'match' => 'equals',
  634. 'limit' => 1,
  635. );
  636. $references = node_reference_potential_references($field, $options);
  637. if ($references) {
  638. // @todo The best thing would be to present the user with an
  639. // additional form, allowing the user to choose between valid
  640. // candidates with the same title. ATM, we pick the first
  641. // matching candidate...
  642. $nid = key($references);
  643. }
  644. else {
  645. form_error($element, t('%name: found no valid post with that title.', array('%name' => $instance['label'])));
  646. }
  647. }
  648. }
  649. // Set the element's value as the node id that was extracted from the entered
  650. // input.
  651. form_set_value($element, $nid, $form_state);
  652. }
  653. /**
  654. * Implements hook_field_widget_error().
  655. */
  656. function node_reference_field_widget_error($element, $error, $form, &$form_state) {
  657. form_error($element['nid'], $error['message']);
  658. }
  659. /**
  660. * Builds a list of referenceable nodes suitable for the '#option' FAPI property.
  661. *
  662. * Warning: the function does NOT take care of encoding or escaping the node
  663. * titles. Proper massaging needs to be performed by the caller, according to
  664. * the destination FAPI '#type' (radios / checkboxes / select).
  665. *
  666. * @param $field
  667. * The field definition.
  668. * @param $flat
  669. * Whether optgroups are allowed.
  670. *
  671. * @return
  672. * An array of referenceable node titles, keyed by node id. If the $flat
  673. * parameter is TRUE, the list might be nested by optgroup first.
  674. */
  675. function _node_reference_options($field, $flat = TRUE) {
  676. $references = node_reference_potential_references($field);
  677. $options = array();
  678. foreach ($references as $key => $value) {
  679. if (empty($value['group']) || $flat) {
  680. $options[$key] = $value['rendered'];
  681. }
  682. else {
  683. // The group name, displayed in selects, cannot contain tags, and should
  684. // have HTML entities unencoded.
  685. $group = html_entity_decode(strip_tags($value['group']), ENT_QUOTES);
  686. $options[$group][$key] = $value['rendered'];
  687. }
  688. }
  689. return $options;
  690. }
  691. /**
  692. * Retrieves an array of candidate referenceable nodes.
  693. *
  694. * This info is used in various places (allowed values, autocomplete
  695. * results, input validation...). Some of them only need the nids,
  696. * others nid + titles, others yet nid + titles + rendered row (for
  697. * display in widgets).
  698. *
  699. * The array we return contains all the potentially needed information,
  700. * and lets consumers use the parts they actually need.
  701. *
  702. * @param $field
  703. * The field definition.
  704. * @param $options
  705. * An array of options to limit the scope of the returned list. The following
  706. * key/value pairs are accepted:
  707. * - string: string to filter titles on (used by autocomplete).
  708. * - match: operator to match the above string against, can be any of:
  709. * 'contains', 'equals', 'starts_with'. Defaults to 'contains'.
  710. * - ids: array of specific node ids to lookup.
  711. * - limit: maximum size of the the result set. Defaults to 0 (no limit).
  712. *
  713. * @return
  714. * An array of valid nodes in the form:
  715. * array(
  716. * nid => array(
  717. * 'title' => The node title,
  718. * 'rendered' => The text to display in widgets (can be HTML)
  719. * ),
  720. * ...
  721. * )
  722. */
  723. function node_reference_potential_references($field, $options = array()) {
  724. // Fill in default options.
  725. $options += array(
  726. 'string' => '',
  727. 'match' => 'contains',
  728. 'ids' => array(),
  729. 'limit' => 0,
  730. );
  731. $results = &drupal_static(__FUNCTION__, array());
  732. // Create unique id for static cache.
  733. $cid = $field['field_name'] . ':' . $options['match'] . ':'
  734. . ($options['string'] !== '' ? $options['string'] : implode('-', $options['ids']))
  735. . ':' . $options['limit'];
  736. if (!isset($results[$cid])) {
  737. $references = FALSE;
  738. if (module_exists('views') && !empty($field['settings']['view']['view_name'])) {
  739. $references = _node_reference_potential_references_views($field, $options);
  740. }
  741. if ($references === FALSE) {
  742. $references = _node_reference_potential_references_standard($field, $options);
  743. }
  744. // Store the results.
  745. $results[$cid] = !empty($references) ? $references : array();
  746. }
  747. return $results[$cid];
  748. }
  749. /**
  750. * Helper function for node_reference_potential_references().
  751. *
  752. * Case of Views-defined referenceable nodes.
  753. */
  754. function _node_reference_potential_references_views($field, $options) {
  755. $settings = $field['settings']['view'];
  756. $options['title_field'] = 'title';
  757. return references_potential_references_view('node', $settings['view_name'], $settings['display_name'], $settings['args'], $options);
  758. }
  759. /**
  760. * Helper function for node_reference_potential_references().
  761. *
  762. * List of referenceable nodes defined by content types.
  763. */
  764. function _node_reference_potential_references_standard($field, $options) {
  765. // Avoid useless work
  766. if (!count($field['settings']['referenceable_types'])) {
  767. return array();
  768. }
  769. $query = db_select('node', 'n');
  770. $node_nid_alias = $query->addField('n', 'nid');
  771. $node_title_alias = $query->addField('n', 'title', 'node_title');
  772. $node_type_alias = $query->addField('n', 'type', 'node_type');
  773. $query->addTag('node_access')
  774. ->addMetaData('id', ' _node_reference_potential_references_standard')
  775. ->addMetaData('field', $field)
  776. ->addMetaData('options', $options);
  777. if (is_array($field['settings']['referenceable_types'])) {
  778. $query->condition('n.type', $field['settings']['referenceable_types'], 'IN');
  779. }
  780. if ($options['string'] !== '') {
  781. switch ($options['match']) {
  782. case 'contains':
  783. $query->condition('n.title', '%' . $options['string'] . '%', 'LIKE');
  784. break;
  785. case 'starts_with':
  786. $query->condition('n.title', $options['string'] . '%', 'LIKE');
  787. break;
  788. case 'equals':
  789. default: // no match type or incorrect match type: use "="
  790. $query->condition('n.title', $options['string']);
  791. break;
  792. }
  793. }
  794. if ($options['ids']) {
  795. $query->condition('n.nid', $options['ids'], 'IN');
  796. }
  797. if ($options['limit']) {
  798. $query->range(0, $options['limit']);
  799. }
  800. $query
  801. ->orderBy($node_title_alias)
  802. ->orderBy($node_type_alias);
  803. $result = $query->execute()->fetchAll();
  804. $references = array();
  805. foreach ($result as $node) {
  806. $references[$node->nid] = array(
  807. 'title' => $node->node_title,
  808. 'rendered' => check_plain($node->node_title),
  809. );
  810. }
  811. return $references;
  812. }
  813. /**
  814. * Menu callback for the autocomplete results.
  815. */
  816. function node_reference_autocomplete($entity_type, $bundle, $field_name, $string = '') {
  817. $field = field_info_field($field_name);
  818. $instance = field_info_instance($entity_type, $field_name, $bundle);
  819. $options = array(
  820. 'string' => $string,
  821. 'match' => $instance['widget']['settings']['autocomplete_match'],
  822. 'limit' => 10,
  823. );
  824. $references = node_reference_potential_references($field, $options);
  825. $matches = array();
  826. foreach ($references as $id => $row) {
  827. // Markup is fine in autocompletion results (might happen when rendered
  828. // through Views) but we want to remove hyperlinks.
  829. $suggestion = preg_replace('/<a href="([^<]*)">([^<]*)<\/a>/', '$2', $row['rendered']);
  830. // Add a class wrapper for a few required CSS overrides.
  831. $matches[$row['title'] . " [nid:$id]"] = '<div class="reference-autocomplete">' . $suggestion . '</div>';
  832. }
  833. drupal_json_output($matches);
  834. }
  835. /**
  836. * Implements hook_node_type_update().
  837. *
  838. * Reflect type name changes to the 'referenceable types' settings: when
  839. * the name of a type changes, the change needs to be reflected in the
  840. * "referenceable types" setting for any node_reference field
  841. * referencing it.
  842. */
  843. function node_reference_node_type_update($info) {
  844. if (!empty($info->old_type) && $info->old_type != $info->type) {
  845. $fields = field_info_fields();
  846. foreach ($fields as $field_name => $field) {
  847. if ($field['type'] == 'node_reference' && isset($field['settings']['referenceable_types'][$info->old_type])) {
  848. $field['settings']['referenceable_types'][$info->type] = empty($field['settings']['referenceable_types'][$info->old_type]) ? 0 : $info->type;
  849. unset($field['settings']['referenceable_types'][$info->old_type]);
  850. field_update_field($field);
  851. }
  852. }
  853. }
  854. }
  855. /**
  856. * Theme preprocess function.
  857. *
  858. * Allows specific node templates for nodes displayed as values of a
  859. * node_reference field with a specific view mode.
  860. */
  861. function node_reference_preprocess_node(&$vars) {
  862. // The 'referencing_field' attribute of the node is added by the
  863. // node_reference_node mode formatter (display referenced node
  864. // in a specific view mode).
  865. if (!empty($vars['node']->referencing_field)) {
  866. $node = $vars['node'];
  867. $field = $node->referencing_field;
  868. $vars['theme_hook_suggestions'][] = 'node_reference';
  869. $vars['theme_hook_suggestions'][] = 'node_reference__' . $field['field_name'];
  870. $vars['theme_hook_suggestions'][] = 'node_reference__' . $node->type;
  871. $vars['theme_hook_suggestions'][] = 'node_reference__' . $field['field_name'] . '__' . $node->type;
  872. }
  873. }
  874. /**
  875. * Implements hook_field_prepare_translation().
  876. *
  877. * When preparing a translation, load any translations of existing
  878. * references.
  879. * @todo Correctly implement after http://drupal.org/node/362021 is fixed.
  880. */
  881. function node_reference_field_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items) {
  882. $addition = array();
  883. $addition[$field['field_name']] = array();
  884. if (isset($entity->translation_source->$field['field_name'])
  885. && is_array($entity->translation_source->$field['field_name'])) {
  886. foreach ($entity->translation_source->$field['field_name'] as $key => $reference) {
  887. $reference_node = node_load($reference['nid']);
  888. // Test if the referenced node type is translatable and, if so,
  889. // load translations if the reference is not for the current language.
  890. // We can assume the translation module is present because it invokes 'prepare translation'.
  891. if (translation_supported_type($reference_node->type)
  892. && !empty($reference_node->language)
  893. && $reference_node->language != $node->language
  894. && $translations = translation_node_get_translations($reference_node->tnid)) {
  895. // If there is a translation for the current language, use it.
  896. $addition[$field['field_name']][] = array(
  897. 'nid' => isset($translations[$node->language])
  898. ? $translations[$node->language]->nid
  899. : $reference['nid'],
  900. );
  901. }
  902. }
  903. }
  904. return $addition;
  905. }
  906. /**
  907. * Implements hook_options_list().
  908. */
  909. function node_reference_options_list($field) {
  910. return _node_reference_options($field, FALSE);
  911. }
  912. /**
  913. * Implements hook_content_migrate_field_alter().
  914. *
  915. * Use this to tweak the conversion of field settings from the D6 style to the
  916. * D7 style for specific situations not handled by basic conversion, as when
  917. * field types or settings are changed.
  918. *
  919. * $field_value['widget_type'] is available to
  920. * see what widget type was originally used.
  921. */
  922. function node_reference_content_migrate_field_alter(&$field_value, $instance_value) {
  923. switch ($field_value['module']) {
  924. case 'nodereference':
  925. $field_value['module'] = 'node_reference';
  926. $field_value['type'] = 'node_reference';
  927. // Translate 'view' settings.
  928. $view_name = isset($field_value['settings']['advanced_view']) ? $field_value['settings']['advanced_view'] : '';
  929. $view_args = isset($field_value['settings']['advanced_view_args']) ? $field_value['settings']['advanced_view_args'] : '';
  930. $view_args = array_map('trim', explode(',', $view_args));
  931. $field_value['settings']['view'] = array(
  932. 'view_name' => $view_name,
  933. 'display_name' => 'default',
  934. 'args' => $view_args,
  935. );
  936. if ($view_name) {
  937. $field_value['messages'][] = t("The field uses the view @view_name to determine referenceable nodes. You will need to manually edit the view and add a display of type 'References'.");
  938. }
  939. unset($field_value['settings']['advanced_view']);
  940. unset($field_value['settings']['advanced_view_args']);
  941. break;
  942. }
  943. }
  944. /**
  945. * Implements hook_content_migrate_instance_alter().
  946. *
  947. * Use this to tweak the conversion of instance or widget settings from the D6
  948. * style to the D7 style for specific situations not handled by basic
  949. * conversion, as when formatter or widget names or settings are changed.
  950. */
  951. function node_reference_content_migrate_instance_alter(&$instance_value, $field_value) {
  952. switch ($field_value['type']) {
  953. case 'nodereference':
  954. // Massage formatters.
  955. foreach ($instance_value['display'] as $context => &$display) {
  956. switch ($display['type']) {
  957. case 'full':
  958. case 'teaser':
  959. // Those two formatters have been merged into
  960. // 'node_reference_view_mode', with a formatter setting.
  961. $display['type'] = 'node_reference_node';
  962. $display['settings']['node_reference_view_mode'] = $display['type'];
  963. break;
  964. default:
  965. // The formatter names changed, all are prefixed with
  966. // 'node_reference_'.
  967. $display['type'] = 'node_reference_' . $display['type'];
  968. break;
  969. }
  970. }
  971. // Massage the widget.
  972. switch ($instance_value['widget']['type']) {
  973. case 'nodereference_autocomplete':
  974. $instance_value['widget']['type'] = 'node_reference_autocomplete';
  975. $instance_value['widget']['module'] = 'node_reference';
  976. break;
  977. case 'nodereference_select':
  978. $instance_value['widget']['type'] = 'options_select';
  979. $instance_value['widget']['module'] = 'options';
  980. break;
  981. case 'nodereference_buttons':
  982. $instance_value['widget']['type'] = 'options_buttons';
  983. $instance_value['widget']['module'] = 'options';
  984. }
  985. break;
  986. }
  987. }
  988. /**
  989. * Implements hook_field_views_data().
  990. *
  991. * In addition to the default field information we add the relationship for
  992. * views to connect back to the node table.
  993. */
  994. function node_reference_field_views_data($field) {
  995. // No module_load_include(): this hook is invoked from
  996. // views/modules/field.views.inc, which is where that function is defined.
  997. $data = field_views_field_default_views_data($field);
  998. $storage = $field['storage']['details']['sql'];
  999. foreach ($storage as $age => $table_data) {
  1000. $table = key($table_data);
  1001. $columns = current($table_data);
  1002. $id_column = $columns['nid'];
  1003. if (isset($data[$table])) {
  1004. // Filter: swap the handler to the 'in' operator. The callback receives
  1005. // the field name instead of the whole $field structure to keep views
  1006. // data to a reasonable size.
  1007. $data[$table][$id_column]['filter']['handler'] = 'views_handler_filter_in_operator';
  1008. $data[$table][$id_column]['filter']['options callback'] = 'node_reference_views_options';
  1009. $data[$table][$id_column]['filter']['options arguments'] = array($field['field_name']);
  1010. // Argument: display node.title in argument titles (handled in our custom
  1011. // handler) and summary lists (handled by the base views_handler_argument
  1012. // handler).
  1013. // Both mechanisms rely on the 'name table' and 'name field' information
  1014. // below, by joining to a separate copy of the base table from the field
  1015. // data table.
  1016. $data[$table][$id_column]['argument']['handler'] = 'references_handler_argument';
  1017. $data[$table][$id_column]['argument']['name table'] = $table . '_reference';
  1018. $data[$table][$id_column]['argument']['name field'] = 'title';
  1019. $data[$table . '_reference']['table']['join'][$table] = array(
  1020. 'left_field' => $id_column,
  1021. 'table' => 'node',
  1022. 'field' => 'nid',
  1023. );
  1024. // Relationship.
  1025. $data[$table][$id_column]['relationship'] = array(
  1026. 'handler' => 'references_handler_relationship',
  1027. 'base' => 'node',
  1028. 'base field' => 'nid',
  1029. 'field' => $id_column,
  1030. 'label' => $field['field_name'],
  1031. 'field_name' => $field['field_name'],
  1032. );
  1033. }
  1034. }
  1035. return $data;
  1036. }
  1037. /**
  1038. * Implements hook_field_views_data_views_data_alter().
  1039. */
  1040. function node_reference_field_views_data_views_data_alter(&$data, $field) {
  1041. foreach ($field['bundles'] as $entity_type => $bundles) {
  1042. $entity_info = entity_get_info($entity_type);
  1043. $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type;
  1044. list($label, $all_labels) = field_views_field_label($field['field_name']);
  1045. $entity = $entity_info['label'];
  1046. if ($entity == t('Node')) {
  1047. $entity = t('Content');
  1048. }
  1049. // Only specify target entity type if the field is used in more than one.
  1050. if (count($field['bundles']) > 1) {
  1051. $title = t('@field (@field_name) - reverse (to @entity)', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name']));
  1052. }
  1053. else {
  1054. $title = t('@field (@field_name) - reverse', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name']));
  1055. }
  1056. $data['node'][$pseudo_field_name]['relationship'] = array(
  1057. 'title' => $title,
  1058. 'help' => t('Relate each @entity referencing the node through @field.', array('@entity' => $entity, '@field' => $label)),
  1059. 'handler' => 'views_handler_relationship_entity_reverse',
  1060. 'field_name' => $field['field_name'],
  1061. 'field table' => _field_sql_storage_tablename($field),
  1062. 'field field' => $field['field_name'] . '_nid',
  1063. 'base' => $entity_info['base table'],
  1064. 'base field' => $entity_info['entity keys']['id'],
  1065. 'label' => t('!field_name', array('!field_name' => $field['field_name'])),
  1066. );
  1067. }
  1068. }
  1069. /**
  1070. * Helper callback for the views_handler_filter_in_operator filter.
  1071. *
  1072. * @param $field_name
  1073. * The field name.
  1074. */
  1075. function node_reference_views_options($field_name) {
  1076. $options = array();
  1077. if ($field = field_info_field($field_name)) {
  1078. $options = _node_reference_options($field, TRUE);
  1079. // The options will be used as is in checkboxes, and thus need to be
  1080. // sanitized first.
  1081. foreach ($options as $key => $value) {
  1082. $options[$key] = field_filter_xss($value);
  1083. }
  1084. }
  1085. return $options;
  1086. }