--- nodereference.module.47	2006-08-21 11:02:42.000000000 +0200
+++ nodereference.module	2006-09-22 01:48:34.845577800 +0200
@@ -23,8 +23,13 @@ function nodereference_menu($may_cache) 
   $items = array();
 
   if ($may_cache) {
-    $items[] = array('path' => 'nodereference/autocomplete', 'title' => t('node reference autocomplete'),
-      'callback' => 'nodereference_autocomplete', 'access' => user_access('access content'), 'type' => MENU_CALLBACK);
+    $items[] = array(
+      'path' => 'nodereference/autocomplete',
+      'title' => t('node reference autocomplete'),
+      'callback' => 'nodereference_autocomplete',
+      'access' => user_access('access content'),
+      'type' => MENU_CALLBACK,
+      );
   }
 
   return $items;
@@ -35,7 +40,8 @@ function nodereference_menu($may_cache) 
  */
 function nodereference_field_info() {
   return array(
-    'nodereference' => array('label' => 'Node Reference'),
+    'nodereference' => array('label' => t('Node Reference')),
+    'nodereferrers' => array('label' => t('Node Referrers')),
   );
 }
 
@@ -43,8 +49,8 @@ function nodereference_field_info() {
  * Implementation of hook_field_settings().
  */
 function nodereference_field_settings($op, $field) {
-  switch ($op) {
-    case 'form':
+  switch ($field['type'] .'/'. $op) {
+    case 'nodereference/form':
       $form = array();
       $form['referenceable_types'] = array(
         '#type' => 'checkboxes',
@@ -55,27 +61,87 @@ function nodereference_field_settings($o
       );
       return $form;
 
-    case 'save':
+    case 'nodereference/save':
       return array('referenceable_types');
 
-    case 'database columns':
+    case 'nodereferrers/form':
+      $form = array();
+      $form['referrer_types'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Referrer types to be displayed in list.'),
+        '#multiple' => TRUE,
+        '#default_value' => isset($field['referrer_types']) ? $field['referrer_types'] : array(),
+        '#options' => node_get_types(),
+      );
+    return $form;
+      
+    case 'nodereferrers/save':
+      return array('referrer_types');
+
+    case 'nodereference/database columns':
+    case 'nodereferrers/database columns':
       $columns = array(
         'nid' => array('type' => 'int', 'not null' => TRUE, 'default' => '0'),
       );
       return $columns;
+
   }
 }
 
 /**
  * Implementation of hook_field().
+ * 
+ * @todo Remove not needed case nodereferrers/insert.
+ * @todo Search & destroy any backlinks in new $op nodereference/delete.
  */
-function nodereference_field($op, &$node, $field, &$items, $teaser, $page) {
-  switch ($op) {
-    case 'view':
-      foreach ($items as $delta => $item) {
-        $items[$delta]['view'] = content_format($field, $item, 'default', $node);
+function nodereference_field($op, &$node, $field, &$node_field, $teaser, $page) {
+  switch ($field['type'] .'/'. $op) {
+    case 'nodereference/view':
+      foreach ($node_field as $delta => $item) {
+        $node_field[$delta]['view'] = content_format($field, $item, $field['reference_output'], $node);
+      }
+      return theme('field', $node, $field, $node_field, $teaser, $page);
+      
+    case 'nodereference/load':
+      foreach ($node_field as $delta => $item) {
+        $node_field[$delta]['node'] = node_load($item['nid']);
+      }
+      return array($field['field_name'] => $node_field);
+      
+    case 'nodereferrers/view':
+      // Pass referring node objects to nodereference theming and
+      // pass the *array* of themed views to content theme_field(). 24/08/2006 sun
+      $output = array();
+      foreach ($node_field as $delta => $item) {
+        $output[$delta]['view'] = theme('nodereference', $field, $item);
+      }
+      return theme('field', $node, $field, $output, $teaser, $page);
+
+    case 'nodereferrers/insert':
+      // Assign the node's nid to the referrers data. This helps retrieving the node's referrers.
+      $node_field[0]['nid'] = $node->nid;
+      return;
+
+    case 'nodereferrers/load':
+      $types = array_values($field['referrer_types']);
+      $values = nodereference_get_referrers($node->nid, $types);
+
+      // Clear $node_field containing the displayed node id currently.
+      $node_field = array();
+      
+      // Pass referring node objects into CCK content_load() cache. 24/08/2006 sun
+      foreach ($values as $nid => $rnode) {
+        $node_field[] = $rnode;
+      }
+      return array($field['field_name'] => $node_field);
+      
+    case 'nodereference/update':
+    case 'nodereferrers/update':
+      // clear cache for referring nodes to update their views
+      foreach (nodereference_get_referrers($node->nid) as $delta => $item) {
+        cache_clear_all('content:'. $item->nid .':'. $item->vid);
       }
-      return theme('field', $node, $field, $items, $teaser, $page);
+      return;
   }
 }
 
@@ -85,11 +151,11 @@ function nodereference_field($op, &$node
 function nodereference_field_formatter_info() {
   return array(
     'default' => array(
-      'label' => 'Default',
+      'label' => 'Default (Link)',
       'field types' => array('nodereference'),
     ),
     'plain' => array(
-      'label' => 'Plain text',
+      'label' => 'Plain Text',
       'field types' => array('nodereference'),
     ),
   );
@@ -100,11 +166,8 @@ function nodereference_field_formatter_i
  */
 function nodereference_field_formatter($field, $item, $formatter, $node) {
   $text = '';
-  if (isset($item['nid'])) {
-    $referenced_node = node_load($item['nid']);
-    if ($referenced_node) {
-      $text = l($referenced_node->title, 'node/'. $referenced_node->nid);
-    }
+  if (is_object($item['node'])) {
+    $text = l($item['node']->title, 'node/'. $item['node']->nid);
   }
 
   switch ($formatter) {
@@ -117,18 +180,50 @@ function nodereference_field_formatter($
 }
 
 /**
+ * Implementation of hook_field_view_item().
+ * 
+ * @todo Has to be moved to hook_field_formatter() 24/08/2006 sun
+ */
+function nodereference_field_view_item($field, $item) {
+  if (!isset($item['nid'])) {
+    return '';
+  }
+  
+  switch($field['type']) {
+    case 'nodereference':
+      $referenced_node = node_load($item['nid']);
+      if ($referenced_node) {
+        return theme('nodereference', $field, $referenced_node);
+      }
+      break;
+
+    case 'nodereferrers':
+      // Should/could these be retrieved on hook_field($op='load')?
+      $referrers = nodereference_get_referrers($item['nid']);
+      foreach($referrers as $referrer) {
+        $output .= theme('nodereference', $field, $referrer);
+      }
+      return $output;
+  }
+}
+
+/**
  * Implementation of hook_widget_info().
  */
 function nodereference_widget_info() {
   return array(
     'nodereference_select' => array(
-      'label' => 'Select List',
+      'label' => t('Select List'),
       'field types' => array('nodereference'),
     ),
     'nodereference_autocomplete' => array(
-      'label' => 'Autocomplete Text Field',
+      'label' => t('Autocomplete Text Field'),
       'field types' => array('nodereference'),
     ),
+    'nodereferrers_list' => array(
+      'label' => t('Read-Only List'),
+      'field types' => array('nodereferrers'),
+    ),
   );
 }
 
@@ -136,120 +231,119 @@ function nodereference_widget_info() {
  * Implementation of hook_widget().
  */
 function nodereference_widget($op, &$node, $field, &$node_field) {
-  if ($field['widget']['type'] == 'nodereference_select') {
-    switch ($op) {
-      case 'prepare form values':
-        $node_field_transposed = content_transpose_array_rows_cols($node_field);
-        $node_field['default nids'] = $node_field_transposed['nid'];
-        break;
-
-      case 'form':
-        $form = array();
-
-        $form[$field['field_name']] = array('#tree' => TRUE);
-        $form[$field['field_name']]['nids'] = array(
-          '#type' => 'select',
-          '#title' => t($field['widget']['label']),
-          '#default_value' => $node_field['default nids'],
-          '#multiple' => $field['multiple'],
-          '#options' => _nodereference_potential_references($field),
-          '#required' => $field['required'],
-          '#description' => $field['widget']['description'],
-        );
-
-        return $form;
+  switch($field['widget']['type'] .'/'. $op) {
+    case 'nodereference_select/prepare form values':
+      $node_field_transposed = content_transpose_array_rows_cols($node_field);
+      $node_field['default nids'] = $node_field_transposed['nid'];
+      break;
 
-      case 'process form values':
-        if ($field['multiple']) {
-          $node_field = content_transpose_array_rows_cols(array('nid' => $node_field['nids']));
-        }
-        else {
-          $node_field[0]['nid'] = $node_field['nids'];
-        }
-        // Remove the widget's data representation so it isn't saved.
-        unset($node_field['nids']);
-    }
-  }
-  else {
-    switch ($op) {
-      case 'prepare form values':
-        foreach ($node_field as $delta => $item) {
-          $node_field[$delta]['default node_name'] = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $node_field[$delta]['nid']));
-        }
-        break;
+    case 'nodereference_select/form':
+      $form = array();
+      $form[$field['field_name']] = array('#tree' => TRUE);
+      $form[$field['field_name']]['nids'] = array(
+        '#type' => 'select',
+        '#title' => t($field['widget']['label']),
+        '#default_value' => $node_field['default nids'],
+        '#multiple' => $field['multiple'],
+        '#options' => _nodereference_potential_references($field),
+        '#required' => $field['required'],
+        '#description' => $field['widget']['description'],
+      );
+      return $form;
 
-      case 'form':
-        $form = array();
-        $form[$field['field_name']] = array('#tree' => TRUE);
-
-        if ($field['multiple']) {
-          $form[$field['field_name']]['#type'] = 'fieldset';
-          $form[$field['field_name']]['#title'] = t($field['widget']['label']);
-          $form[$field['field_name']]['#description'] = $field['widget']['description'];
-          $delta = 0;
-          foreach ($node_field as $item) {
-            if ($item['nid']) {
-              $form[$field['field_name']][$delta]['node_name'] = array(
-                '#type' => 'textfield',
-                '#title' => '',
-                '#autocomplete_path' => 'nodereference/autocomplete/'. $field['field_name'],
-                '#default_value' => $item['default node_name'],
-                '#required' => ($delta == 0) ? $field['required'] : FALSE,
-              );
-              $delta++;
-            }
-          }
-          foreach (range($delta, $delta + 2) as $delta) {
+    case 'nodereference_select/process form values':
+      if ($field['multiple']) {
+         $node_field = content_transpose_array_rows_cols(array('nid' => $node_field['nids']));
+      }
+      else {
+        $node_field[0]['nid'] = $node_field['nids'];
+      }
+      // Remove the widget's data representation so it isn't saved.
+      unset($node_field['nids']);
+      break;
+
+    case 'nodereference_autocomplete/prepare form values':
+      foreach ($node_field as $delta => $item) {
+        $node_field[$delta]['default node_name'] = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), $node_field[$delta]['nid']));
+      }
+      break;
+  
+    case 'nodereference_autocomplete/form':
+      $form = array();
+      $form[$field['field_name']] = array('#tree' => TRUE);
+      if ($field['multiple']) {
+        $form[$field['field_name']]['#type'] = 'fieldset';
+        $form[$field['field_name']]['#title'] = t($field['widget']['label']);
+        $form[$field['field_name']]['#description'] = $field['widget']['description'];
+        $delta = 0;
+        foreach ($node_field as $item) {
+          if ($item['nid']) {
             $form[$field['field_name']][$delta]['node_name'] = array(
               '#type' => 'textfield',
               '#title' => '',
               '#autocomplete_path' => 'nodereference/autocomplete/'. $field['field_name'],
-              '#default_value' => '',
+              '#default_value' => $item['default node_name'],
               '#required' => ($delta == 0) ? $field['required'] : FALSE,
             );
+            $delta++;
           }
         }
-        else {
-          $form[$field['field_name']][0]['node_name'] = array(
+        foreach (range($delta, $delta + 2) as $delta) {
+          $form[$field['field_name']][$delta]['node_name'] = array(
             '#type' => 'textfield',
-            '#title' => t($field['widget']['label']),
+            '#title' => '',
             '#autocomplete_path' => 'nodereference/autocomplete/'. $field['field_name'],
-            '#default_value' => $node_field[0]['default node_name'],
-            '#required' => $field['required'],
-            '#description' => $field['widget']['description'],
+            '#default_value' => '',
+            '#required' => ($delta == 0) ? $field['required'] : FALSE,
           );
         }
-        return $form;
+      }
+      else {
+        $form[$field['field_name']][0]['node_name'] = array(
+          '#type' => 'textfield',
+          '#title' => t($field['widget']['label']),
+          '#autocomplete_path' => 'nodereference/autocomplete/'. $field['field_name'],
+          '#default_value' => $node_field[0]['default node_name'],
+          '#required' => $field['required'],
+          '#description' => $field['widget']['description'],
+        );
+      }
+      return $form;
 
-      case 'validate':
-        foreach ($node_field as $delta => $item) {
-          if ($item['node_name'] && !in_array($item['node_name'], _nodereference_potential_references($field))) {
-            form_set_error($field['field_name'], t('No post with that title exists.'));
-          }
+    case 'nodereference_autocomplete/validate':
+      foreach ($node_field as $delta => $item) {
+        if ($item['node_name'] && !in_array($item['node_name'], _nodereference_potential_references($field))) {
+          form_set_error($field['field_name'], t('No post with that title exists.'));
         }
-        return;
+      }
+      return;
 
-      case 'process form values':
-        foreach ($node_field as $delta => $item) {
-          $nid = 0;
-          if ($node_field[$delta]['node_name']) {
-            $nid = db_result(db_query(db_rewrite_sql("SELECT n.nid FROM {node} n WHERE n.title = '%s'"), $node_field[$delta]['node_name']));
-          }
-          // Remove the widget's data representation so it isn't saved.
-          unset($node_field[$delta]['node_name']);
-          if ($nid) {
-            $node_field[$delta]['nid'] = $nid;
-          }
-          else {
-            unset($node_field[$delta]);
-          }
+    case 'nodereference_autocomplete/process form values':
+      foreach ($node_field as $delta => $item) {
+        $nid = db_result(db_query(db_rewrite_sql("SELECT n.nid FROM {node} n  WHERE n.title = '%s'"), $node_field[$delta]['node_name']));
+
+        // Remove the widget's data representation so it isn't saved.
+        unset($node_field[$delta]['node_name']);
+        if ($nid) {
+          $node_field[$delta]['nid'] = $nid;
         }
-        break;
-    }
+        else {
+          unset($node_field[$delta]);
+        }
+      }
+      break;
+
+    case 'nodereferrers_list/form':
+      $form = array();
+      $form[$field['field_name']] = array('#tree' => TRUE);
+      $form[$field['field_name']][0]['nid'] = array(
+        '#type' => 'hidden',
+        '#value' => $node->nid,
+        );
+      return $form;
   }
 }
 
-
 /**
  * Fetch an array of all candidate referenced nodes, for use in presenting the selection form to the user.
  */
@@ -259,17 +353,15 @@ function _nodereference_potential_refere
   if (isset($field['referenceable_types'])) {
     foreach ($field['referenceable_types'] as $related_type) {
       if ($related_type) {
-        $related_types[] = " type = '". $related_type ."'";
+        $related_types[] = " n.type = '". $related_type ."'";
       }
     }
   }
-
-  $related_clause = implode(' OR ', $related_types);
-
   if (!count($related_types)) {
     return array();
   }
 
+  $related_clause = implode(' OR ', $related_types);
   $result = db_query(db_rewrite_sql("SELECT n.nid, n.title, n.type FROM {node} n WHERE ". $related_clause ." ORDER BY n.title, n.type"));
 
   if (db_num_rows($result) == 0) {
@@ -277,7 +369,6 @@ function _nodereference_potential_refere
   }
 
   $rows = array();
-
   while ($node = db_fetch_object($result)) {
     if ($return_full_nodes) {
       $rows[$node->nid] = $node;
@@ -286,7 +377,6 @@ function _nodereference_potential_refere
       $rows[$node->nid] = $node->title;
     }
   }
-
   return $rows;
 }
 
@@ -306,4 +396,46 @@ function nodereference_autocomplete($fie
 
   print drupal_to_js($matches);
   exit();
-}
\ No newline at end of file
+}
+
+/**
+ * Get an array of referrers by nid and content type.
+ * @param nid
+ *      referred to nodes nid
+ * @param content_types
+ *   array of content types to be queried for. empty array == all content types
+ * @return array
+ */
+function nodereference_get_referrers($nid, $content_types = array()) {
+  // Filter all content fields to find names of all fields of type nodereference
+  $fields = content_fields();
+  $values = array();
+  foreach($fields as $field) {
+    if ($field['type'] == 'nodereference') {
+      $db_info = content_database_info($field);
+
+      // Gather nodereferences that refer to $nid
+      // Fetch only nid and title for output and vid for caching 24/08/2006 sun
+      $filter_content_types = '';
+      if ($content_types) {
+        $filter_content_types = "AND n.type IN ('". implode("', '", $content_types) ."') ";
+      }
+      $result = db_query(
+        "SELECT       n.nid, n.vid, n.title
+         FROM         {" . $db_info['table'] . "} nr
+         INNER JOIN   {node} n ON n.vid = nr.vid AND n.status = 1 ". $filter_content_types ."
+         WHERE        nr." . $db_info['columns']['nid']['column'] . " = %d
+         ORDER BY     n.created DESC",  $nid);
+      
+      while ($value = db_fetch_object($result)) {
+        // avoid duplicate referrers by using nid as key
+        $values[$value->nid] = $value;
+      }
+    }
+  }
+  return $values;
+}
+
+function theme_nodereference($field, $node) {
+  return '<div class="nodereference">'. l($node->title, 'node/'. $node->nid) .'</div>';
+}
