Index: nodereference.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/cck/nodereference.module,v
retrieving revision 1.39.2.5
diff -u -r1.39.2.5 nodereference.module
--- nodereference.module	19 Jan 2007 11:42:35 -0000	1.39.2.5
+++ nodereference.module	1 Feb 2007 03:42:46 -0000
@@ -27,6 +27,7 @@
 function nodereference_field_info() {
   return array(
     'nodereference' => array('label' => 'Node Reference'),
+    'nodereferrer' => array('label' => 'Node Referrers'),
   );
 }
 
@@ -34,8 +35,8 @@
  * 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',
@@ -67,39 +68,129 @@
       }
       return $form;
 
-    case 'save':
+    case 'nodereference/save':
       $settings = array('referenceable_types');
       if (module_exists('views')) {
         $settings[] = 'advanced_view';
       }
       return $settings;
 
-    case 'database columns':
+    case 'nodereference/database columns':
       $columns = array(
         'nid' => array('type' => 'int', 'not null' => TRUE, 'default' => '0'),
       );
       return $columns;
 
-    case 'filters':
+    case 'nodereference/filters':
       return array(
         'default' => array(
-        'list' => '_nodereference_filter_handler',
-        'list-type' => 'list',
-        'operator' => 'views_handler_operator_or',
-        'value-type' => 'array',
-        'extra' => array('field' => $field),
-      ),
-    );
+          'list' => '_nodereference_filter_handler',
+          'list-type' => 'list',
+          'operator' => 'views_handler_operator_or',
+          'value-type' => 'array',
+          'extra' => array('field' => $field),
+        ),
+      );
+
+    case 'nodereferrer/form':
+      $form = array();
+      $form['referrer_types'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Referring Node Types'),
+        '#multiple' => TRUE,
+        '#default_value' => isset($field['referrer_types']) ? $field['referrer_types'] : array(),
+        '#options' => node_get_types('names'),
+      );  
+
+      $options = array(); 
+      $types = content_fields();
+      foreach($types as $type) {
+        if ($type['type'] == 'nodereference') {
+          $options[$type['field_name']] = $type['widget']['label']; 
+        }
+      }   
+      
+      $form['referrer_fields'] = array (
+        '#type' => 'checkboxes',
+        '#title' => t('Referring Fields'),
+        '#multiple' => TRUE,
+        '#default_value' => isset($field['referrer_fields']) ? $field['referrer_fields'] : array(),
+        '#options' => $options,
+      );
+      return $form;
+
+    case 'nodereferrer/save':
+      return array('referrer_types', 'referrer_fields');
+
   }
 }
 
 /**
  * Implementation of hook_field().
  */
-//function nodereference_field($op, &$node, $field, &$items, $teaser, $page) {
-//  switch ($op) {
-//  }
-//}
+function nodereference_field($op, &$node, $field, &$items, $teaser, $page) {
+  switch ($field['type'] .'/'. $op) {
+    case 'nodereference/load':
+      foreach($items as $delta => $value) {
+
+      }
+      break;
+
+    case 'nodereference/update':
+    case 'nodereference/delete':
+    case 'nodereference/insert':
+      // clear cache for the old nodes that were referenced.
+      // load old node with node_load since $node is the new node.
+      $oldnode = node_load($node->nid);
+      if (count($oldnode->$field['field_name'])) {
+        foreach($oldnode->$field['field_name'] as $item) {
+          $cid = 'content:'. $item['nid'];
+          cache_clear_all($cid, 'cache', TRUE);
+        }
+      }
+
+      // clear cache for the new nodes that are being referenced.
+      if (count($items)) {
+        foreach($items as $item) {
+          $cid = 'content:'. $item['nid'];
+          cache_clear_all($cid, 'cache', TRUE);
+        }
+      }
+      break;
+
+    case 'nodereferrer/load':
+      $types = array_values($field['referrer_types']);
+      $fields = array_values($field['referrer_fields']);
+      $values = nodereferrer_referrers($node->nid, $fields, $types);
+      //$node_field = array(); -- do I really need to initialize... do I get nod
+      // even though this module doesn't have any db fields.
+      $node_field = array();
+      // Pass referring node objects into CCK content_load() cache. 24/08/2006 s
+      foreach ($values as $nid => $rnode) {
+        $node_field[] = $rnode;
+      }
+      return array($field['field_name'] => $node_field);
+
+    case 'nodereferrer/delete':
+    case 'nodereferrer/update':
+      // @todo:
+      //  this should be a more generic delete/update...
+      //  currently it will only clear reference nodes when a node
+      //  with a referrers field is updates. It won't work on nodes
+      //  without a referrers field that are referenced.
+
+      // clear cache on nodes that refer to me.
+      $types = array_values($field['referrer_types']);
+      $fields = array_values($field['referrer_fields']);
+
+      // clear any modules referring to me as my title or other data may change.
+      foreach (nodereferrer_referrers($node->nid, $fields, $types) as $delta => $item) {
+        $cid = 'content:'. $item['nid'];
+        cache_clear_all($cid, 'cache', TRUE);
+      }
+      return;
+  }
+}
 
 /**
  * Implementation of hook_field_formatter_info().
@@ -108,11 +199,11 @@
   return array(
     'default' => array(
       'label' => 'Default',
-      'field types' => array('nodereference'),
+      'field types' => array('nodereference', 'nodereferrer'),
     ),
     'plain' => array(
       'label' => 'Plain text',
-      'field types' => array('nodereference'),
+      'field types' => array('nodereference', 'nodereferrer'),
     ),
   );
 }
@@ -121,21 +212,22 @@
  * Implementation of hook_field_formatter().
  */
 function nodereference_field_formatter($field, $item, $formatter, $node) {
-  $text = '';
-  if (!empty($item['nid'])) {
+  // only display items users are allowed to view
+  if (!empty($item['nid']) && node_access('view', $item)) {
     $referenced_node = node_load($item['nid']);
-    if ($referenced_node) {
-      $text = l($referenced_node->title, 'node/'. $referenced_node->nid);
-    }
-  }
 
-  switch ($formatter) {
-    case 'plain':
-      return strip_tags($text);
+    switch ($formatter) {
+      case 'plain':
+        return check_plain($referenced_node->title);
 
-    default:
-      return $text;
+      default:
+        if ($referenced_node) {
+          return l($referenced_node->title, 'node/'. $referenced_node->nid);
+        }
+        
+    }
   }
+  return '';
 }
 
 /**
@@ -151,6 +243,11 @@
       'label' => 'Autocomplete Text Field',
       'field types' => array('nodereference'),
     ),
+    'nodereferrer_list' => array(
+      'label' => t('Read-Only List'),
+      'field types' => array('nodereferrer'),
+    ),
+
   );
 }
 
@@ -158,172 +255,180 @@
  * 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();
-
-        $options = _nodereference_potential_references($field, true);
-        foreach ($options as $key => $value) {
-          $options[$key] = _nodereference_item($field, $value);
-        }
-        if (!$field['required']) {
-          $options = array(0 => t('<none>')) + $options;
-        }
-
-        $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'],
-          '#size' => min(count($options), 6),
-          '#options' => $options,
-          '#required' => $field['required'],
-          '#description' => $field['widget']['description'],
-        );
+  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 'nodereference_select/form':
+      $form = array();
 
-        return $form;
+      $options = _nodereference_potential_references($field, true);
+      foreach ($options as $key => $value) {
+        $options[$key] = _nodereference_item($field, $value);
+      }
+      if (!$field['required']) {
+        $options = array(0 => t('<none>')) + $options;
+      }
 
-      case 'process form values':
-        if ($field['multiple']) {
-          // if nothing selected, make it 'none'
-          if (empty($node_field['nids'])) {
-            $node_field['nids'] = array(0 => '0');
-          }
-          // drop the 'none' options if other items were also selected
-          elseif (count($node_field['nids']) > 1) {
-            unset($node_field['nids'][0]);
-          }
+      $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'],
+        '#size' => min(count($options), 6),
+        '#options' => $options,
+        '#required' => $field['required'],
+        '#description' => $field['widget']['description'],
+      );
 
-          $node_field = content_transpose_array_rows_cols(array('nid' => $node_field['nids']));
-        }
-        else {
-          $node_field[0]['nid'] = $node_field['nids'];
+      return $form;
+
+    case 'nodereference_select/process form values':
+      if ($field['multiple']) {
+        // if nothing selected, make it 'none'
+        if (empty($node_field['nids'])) {
+          $node_field['nids'] = array(0 => '0');
+        }
+        // drop the 'none' options if other items were also selected
+        elseif (count($node_field['nids']) > 1) {
+          unset($node_field['nids'][0]);
         }
-        // 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) {
-          if (!empty($node_field[$delta]['nid'])) {
-            $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']));
-            $node_field[$delta]['default node_name'] .= ' [nid:'. $node_field[$delta]['nid'] .']';
-          }
+
+        $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']);
+
+    case 'nodereference_autocomplete/prepare form values':
+      foreach ($node_field as $delta => $item) {
+        if (!empty($node_field[$delta]['nid'])) {
+          $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']));
+          $node_field[$delta]['default node_name'] .= ' [nid:'. $node_field[$delta]['nid'] .']';
         }
-        break;
+      }
+      break;
 
-      case 'form':
-        $form = array();
-        $form[$field['field_name']] = array('#tree' => TRUE);
-
-        if ($field['multiple']) {
-          $form[$field['field_name']]['#type'] = 'fieldset';
-          $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' => ($delta == 0) ? t($field['widget']['label']) : '',
-                '#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_autocomplete/form':
+      $form = array();
+      $form[$field['field_name']] = array('#tree' => TRUE);
+
+      if ($field['multiple']) {
+        $form[$field['field_name']]['#type'] = 'fieldset';
+        $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' => ($delta == 0) ? t($field['widget']['label']) : '',
               '#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' => ($delta == 0) ? 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'],
+            '#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) {
-          $error_field = $field['field_name'].']['.$delta.'][node_name';
-          if (!empty($item['node_name'])) {
-            preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $item['node_name'], $matches);
-            if (!empty($matches)) {
-              // explicit nid
-              list(, $title, $nid) = $matches;
-              $refs = _nodereference_potential_references($field, TRUE);
-              if (!in_array($nid, array_keys($refs))) {
-                form_set_error($error_field, t('This post can\'t be referenced.'));
-              }
-              elseif (!empty($title) && ($n = $refs[$nid]) && $title != $n->node_title) {
-                form_set_error($error_field, t('Title mismatch. Please reiterate your selection.'));
-              }
+    case 'nodereference_autocomplete/validate':
+      foreach ($node_field as $delta => $item) {
+        $error_field = $field['field_name'].']['.$delta.'][node_name';
+        if (!empty($item['node_name'])) {
+          preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $item['node_name'], $matches);
+          if (!empty($matches)) {
+            // explicit nid
+            list(, $title, $nid) = $matches;
+            $refs = _nodereference_potential_references($field, TRUE);
+            if (!in_array($nid, array_keys($refs))) {
+              form_set_error($error_field, t('This post can\'t be referenced.'));
             }
-            else {
-              // no explicit nid
-              $refs = _nodereference_potential_references($field, FALSE, $item['node_name'], TRUE);
-              if (empty($refs)) {
-                form_set_error($error_field, t('No post with that title can be referenced.'));
-              }
+            elseif (!empty($title) && ($n = $refs[$nid]) && $title != $n->node_title) {
+              form_set_error($error_field, t('Title mismatch. Please reiterate your selection.'));
             }
           }
-        }
-        return;
-
-      case 'process form values':
-        foreach ($node_field as $delta => $item) {
-          $nid = 0;
-          if (!empty($item['node_name'])) {
-            preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $item['node_name'], $matches);
-            if (!empty($matches)) {
-              // explicit nid
-              $nid = $matches[2];
-            }
-            else {
-              // no explicit nid
-              // TODO :
-              // the best thing would be to present the user with an additional form,
-              // allowing the user to choose between valid candidates with the same title
-              // ATM, we pick the first matching candidate...
-              $nids = _nodereference_potential_references($field, false, $item['node_name'], true);
-              $nid = array_shift(array_keys($nids));
+          else {
+            // no explicit nid
+            $refs = _nodereference_potential_references($field, FALSE, $item['node_name'], TRUE);
+            if (empty($refs)) {
+              form_set_error($error_field, t('No post with that title can be referenced.'));
             }
           }
-          if (!empty($nid)) {
-            $node_field[$delta]['nid'] = $nid;
-            // Remove the widget's data representation so it isn't saved.
-            unset($node_field[$delta]['node_name']);
+        }
+      }
+      return;
+
+    case 'nodereference_autocomplete/process form values':
+      foreach ($node_field as $delta => $item) {
+        $nid = 0;
+        if (!empty($item['node_name'])) {
+          preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $item['node_name'], $matches);
+          if (!empty($matches)) {
+            // explicit nid
+            $nid = $matches[2];
           }
           else {
-            // Don't save empty fields when they're not the first value (keep '0' otherwise)
-            if ($delta > 0) {unset($node_field[$delta]);}
+            // no explicit nid
+            // TODO :
+            // the best thing would be to present the user with an additional form,
+            // allowing the user to choose between valid candidates with the same title
+            // ATM, we pick the first matching candidate...
+            $nids = _nodereference_potential_references($field, false, $item['node_name'], true);
+            $nid = array_shift(array_keys($nids));
           }
         }
-        break;
-    }
+        if (!empty($nid)) {
+          $node_field[$delta]['nid'] = $nid;
+          // Remove the widget's data representation so it isn't saved.
+          unset($node_field[$delta]['node_name']);
+        }
+        else {
+          // Don't save empty fields when they're not the first value (keep '0' otherwise)
+          if ($delta > 0) {unset($node_field[$delta]);}
+        }
+      }
+      break;
+
+    case 'nodereferrer_list/form':
+      // for some reason widgets must implement a form for them to work...
+      // I'm not sure that this is ideal for a set and forget service
+      // automated field, something in content_admin.inc:content_admin_field_overview_form
+      // needs to be re-thunk I think. It seems to be related to the field groups code.
+
+      $form[$field['field_name']] = array(
+        '#type' => 'hidden',
+        '#value' => 'nodereferrer',
+      );
+      return $form;
   }
 }
 
+
 /**
  * Fetch an array of all candidate referenced nodes, for use in presenting the selection form to the user.
  */
@@ -403,6 +508,52 @@
 }
 
 /**
+ * Get an array of referrer nids, by node.type & field.type
+ * @param nid
+ *     the nid we want to find referres for
+ * @param fieldnames
+ *     array of fieldnames to be checked for referrers
+ * @param nodetypes
+ *     array of node types to be checked for referrers
+ */
+
+function nodereferrer_referrers($nid,  $fieldnames = array(), $nodetypes = array()) {
+  if ($nodetypes) {
+    $filter_nodetypes = "AND n.type IN ('". implode("', '", $nodetypes) ."')";
+  }
+  $fields = content_fields();
+  // Set default values of fieldnames..
+  if (!count($fieldnames)) {
+    $fieldnames = array_keys($fields);
+  }
+
+  $values = array();
+  foreach($fieldnames as $fieldname) {
+    if ($fields[$fieldname]['type'] == 'nodereference') {
+      $db_info = content_database_info($fields[$fieldname]);
+
+      $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_nodetypes ."
+                WHERE        nr." . $db_info['columns']['nid']['column'] . " = %d
+                ORDER BY     n.created DESC";
+
+      $query = db_rewrite_sql($query);
+      $result = db_query($query, $nid);
+
+
+      while ($value = db_fetch_array($result)) {
+        // avoid duplicate referrers by using nid as key
+        $values[$value['nid']] = $value;
+      }
+    }
+  }
+  return $values;
+}
+
+
+
+/**
  * Retrieve a pipe delimited string of autocomplete suggestions
  */
 function nodereference_autocomplete($field_name, $string = '') {
@@ -457,4 +608,4 @@
   $options = array(0 => t('<empty>'));
   $options = $options + _nodereference_potential_references($filterinfo['extra']['field']);
   return $options;
-}
\ No newline at end of file
+}
