? .feedfield.module.swp
Index: aggregation_api.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedfield/aggregation_api.inc,v
retrieving revision 1.2
diff -u -p -r1.2 aggregation_api.inc
--- aggregation_api.inc	19 Dec 2006 19:20:09 -0000	1.2
+++ aggregation_api.inc	5 Jan 2010 23:19:27 -0000
@@ -1,16 +1,11 @@
 <?php
 /**
- * $Id: aggregation_api.inc,v 1.2 2006/12/19 19:20:09 thegreenman Exp $
+ * $Id$
  * Aggregation API
- * 
+ *
  * Drupal is a great aggregation platform. This is an attempt to create an API to allow unified
- * access to shared aggregator functions - adding, updating and removing feeds, and querying existing feeds. 
- * 
- */
-
-/**
- * Phase 1: Create a set of functions to work with Aggregator. 
- * Phase 2: Create an extensible system of hooks to allow other modules to implement the API
+ * access to shared aggregator functions - adding, updating and removing feeds, and querying existing feeds.
+ *
  */
 
 /**
@@ -36,16 +31,15 @@
  *   -1 if a conflict exists with an existing feed in the database.
  */
 function aggregation_sync_feed($title, $url) {
-  
   // try to find a feed with the given URL
   $urlfeed   = aggregation_get_feed_by_url($url);
   $titlefeed = aggregation_get_feed_by_title($title);
-  
+
   // if there is no feed with the given url or title, we have an insert
   if (!$urlfeed && !$titlefeed) {
     $newfeed['url']=$url;
- 	  $newfeed['title']=$title;
- 	  return _aggregation_insert_feed($newfeed);
+    $newfeed['title']=$title;
+    return _aggregation_insert_feed($newfeed);
   }
   // if a feed exists with the URL, but none with the given title,
   // we update the title
@@ -59,15 +53,15 @@ function aggregation_sync_feed($title, $
     $titlefeed['url'] = $url;
     return _aggregation_update_feed($titlefeed);
   }
-  
-	// if feeds exists with the given url and title and they are the same,
-	// then there is nothing to update
-	if ($urlfeed['fid'] == $titlefeed['fid']) {
-	  return $urlfeed['fid'];
-	}
-   
-	// else a feed exists with the given URL, and a different feed exists with
-	// the given title, so we have a conflict the user must resolve
+
+  // if feeds exists with the given url and title and they are the same,
+  // then there is nothing to update
+  if ($urlfeed['fid'] == $titlefeed['fid']) {
+    return $urlfeed['fid'];
+  }
+
+  // else a feed exists with the given URL, and a different feed exists with
+  // the given title, so we have a conflict the user must resolve
   return -1;
 
 }
@@ -112,10 +106,10 @@ function aggregation_remove_feed_items($
  * @return 
  *   A feed array
  */
- function aggregation_get_feed_by_url($url) {
-   if (!$url) return FALSE;
-   return db_fetch_array(db_query("SELECT * FROM {aggregator_feed} WHERE url = '%s'", $url));
- }
+function aggregation_get_feed_by_url($url) {
+  if (!$url) return FALSE;
+  return db_fetch_array(db_query("SELECT * FROM {aggregator_feed} WHERE url = '%s'", $url));
+}
 
 /**
  * Load a feed based on it's id
@@ -124,9 +118,9 @@ function aggregation_remove_feed_items($
  * @return
  *   An array of feed data
  */
- function aggregation_get_feed_by_id($fid) {
-   return aggregator_get_feed($fid);
- }
+function aggregation_get_feed_by_id($fid) {
+  return aggregator_feed_load($fid);
+}
 
 
 /**
@@ -136,15 +130,15 @@ function aggregation_remove_feed_items($
  * @return
  * 	 A array of aggregator feed information.
  */
- function aggregation_get_feed_by_title($title) {
-   if (!$title) return FALSE;
-   return db_fetch_array(db_query("SELECT * FROM {aggregator_feed} WHERE title = '%s'", $title));
- }
+function aggregation_get_feed_by_title($title) {
+  if (!$title) return FALSE;
+  return db_fetch_array(db_query("SELECT * FROM {aggregator_feed} WHERE title = '%s'", $title));
+}
 
 
 
 
- //------------------- Aggregator category functions
+//------------------- Aggregator category functions
 
 /**
  * Assign categories to a specific feed 
@@ -152,25 +146,25 @@ function aggregation_remove_feed_items($
  * @param $fid Feed Id 
  * @param $categories An array of categories to assign  
  */
- function aggregation_set_feed_categories($fid,$categories){
-   aggregation_clear_feed_categories($fid);
-   if (is_array($categories)) {
-     foreach ($categories as $cid) {
-       if ($cid) db_query('INSERT INTO {aggregator_category_feed} (fid, cid) VALUES (%d, %d)', $fid, $cid);
-     }
-   }
- }
-
- /**
-  * Remove all the categories assigned to a feed
-  * 
-  * @param $fid Feed Id
-  */
- function aggregation_clear_feed_categories($fid){
-   db_query('DELETE FROM {aggregator_category_feed} WHERE fid=%d', $fid);
- }
- 
- 
+function aggregation_set_feed_categories($fid,$categories){
+  aggregation_clear_feed_categories($fid);
+  if (is_array($categories)) {
+    foreach ($categories as $cid) {
+      if ($cid) db_query('INSERT INTO {aggregator_category_feed} (fid, cid) VALUES (%d, %d)', $fid, $cid);
+    }
+  }
+}
+
+/**
+ * Remove all the categories assigned to a feed
+ * 
+ * @param $fid Feed Id
+ */
+function aggregation_clear_feed_categories($fid){
+  db_query('DELETE FROM {aggregator_category_feed} WHERE fid=%d', $fid);
+}
+
+
 /**
  * Retrieve a list of categories assigned to a feed
  *
@@ -178,36 +172,60 @@ function aggregation_remove_feed_items($
  * @return 
  *   An array of category IDs
  */
- function aggregation_get_assigned_feed_categories($fid){
-   $result=db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = %d', $fid);
-   while($category=db_fetch_array($result)) {
-     $values[]=$category[cid];
-   }
-   return $values;
- }
+function aggregation_get_assigned_feed_categories($fid){
+  $result=db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = %d', $fid);
+  while($category=db_fetch_array($result)) {
+    $values[]=$category['cid'];
+  }
+  return $values;
+}
 
 
+//------------------- Aggregator refresh interval functions
+
+/**
+ * Set the refresh interval of the feed.
+ * 
+ * @param $fid Feed Id
+ * @param $refresh_interval  The refresh interval of the feed.  Should only
+ *   pass in 900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400,
+ *   172800, 259200, 604800, 1209600, or 2419200
+ */
+function aggregation_set_refresh_interval($fid, $refresh_interval) {
+  db_query('UPDATE {aggregator_feed} SET refresh = %d WHERE fid = %d',
+      $refresh_interval, $fid);
+}
+
+/**
+ * Returns the allowed refresh intervals as used by the core aggregator module.
+ * A constant array or enum would be better, but PHP does not natively support those.
+ *
+ * @return An array with integers (in seconds) as the keys,
+ *   and strings in whatever units the "format_interval" function decides.
+ */
+function aggregation_get_allowed_refresh_intervals() {
+  return drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
+}
+
 /**
  * Load all available feed categories
  * 
  * @return A list of options, Id as key, title as value 
  */
- function aggregation_get_all_feed_categories(){
-   static $options;
-   if (!$options) {
-     // get aggregator categories
-     $result = db_query('SELECT title, cid FROM {aggregator_category} ORDER BY title');
-     while ($category = db_fetch_array($result)) {
-      	$options[$category['cid']]=$category['title'];
-     }
-   }
-  	return $options;
- }
-
-
+function aggregation_get_all_feed_categories(){
+  static $options;
+  if (!$options) {
+    // get aggregator categories
+    $result = db_query('SELECT title, cid FROM {aggregator_category} ORDER BY title');
+    while ($category = db_fetch_array($result)) {
+      $options[$category['cid']]=$category['title'];
+    }
+  }
+  return $options;
+}
 
 
- //=================================== Private Functions =================================//
+//=================================== Private Functions =================================//
 
 
 
@@ -220,25 +238,25 @@ function aggregation_remove_feed_items($
  * @return 
  *   A feed id for the inserted feed
  */
- function _aggregation_insert_feed($feed){
-   $fid = db_next_id('{aggregator_feed}_fid');
-   $feed['refresh'] or $feed['refresh']=3600;
-   $feed['block_id'] or $feed['block_id']=5;
-   db_query("INSERT INTO {aggregator_feed} (fid, title, url, refresh, block) VALUES (%d, '%s', '%s', %d, %d)",
-   $fid,
-   $feed['title'],
-   $feed['url'],
-   $feed['refresh'],
-   $feed['block_id']);
-   
-   if (!db_error()) {
-     drupal_set_message(t('A new aggregator feed "%title" was created', array('%title' => $feed['title'])));
-   }
-   else {
-     drupal_set_message(t('A database error occured, no aggregator feed could be created.'), "error");
-   }
-   return $fid;
- }
+function _aggregation_insert_feed($feed){
+  // In the two lines below, the statements will short circuit if the variable
+  // is not null.  Never seen this before, but it works.
+  $feed['refresh'] or $feed['refresh']=3600;
+  $feed['block_id'] or $feed['block_id']=5;
+  db_query("INSERT INTO {aggregator_feed} (title, url, refresh, block) VALUES ('%s', '%s', %d, %d)",
+      $feed['title'],
+      $feed['url'],
+      $feed['refresh'],
+      $feed['block_id']);
+
+  if (!db_error()) {
+    drupal_set_message(t('A new aggregator feed "%title" was created', array('%title' => $feed['title'])));
+  }
+  else {
+    drupal_set_message(t('A database error occured, no aggregator feed could be created.'), "error");
+  }
+  return db_last_insert_id('aggregator_feed', 'fid');
+}
 
 /**
  * Update an existing feed. Do not call directly, use sync_feed
@@ -247,13 +265,13 @@ function aggregation_remove_feed_items($
  * @return 
  *   A feed id for the updated feed
  */
- function _aggregation_update_feed($feed){
-   $feed['refesh'] or $feed['refresh']=3600;
-   db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d",
-   $feed['title'],
-   $feed['url'],
-   $feed['refresh'],
-   $feed['fid']);
-   return $feed['fid'];
- }
-?>
\ No newline at end of file
+function _aggregation_update_feed($feed){
+  $feed['refesh'] or $feed['refresh']=3600;
+  db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d",
+      $feed['title'],
+      $feed['url'],
+      $feed['refresh'],
+      $feed['fid']);
+  return $feed['fid'];
+}
+?>
Index: feedfield.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedfield/feedfield.info,v
retrieving revision 1.1
diff -u -p -r1.1 feedfield.info
--- feedfield.info	17 May 2007 13:30:30 -0000	1.1
+++ feedfield.info	5 Jan 2010 23:19:27 -0000
@@ -1,4 +1,7 @@
-name = Feed field
-description = CCK field to add an aggregation feed
-dependencies = content
-package = CCK
+; $Id$
+name = FeedField
+description = CCK field to add an aggregation feed
+dependencies[] = content
+dependencies[] = aggregator
+package = CCK
+core = 6.x
Index: feedfield.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedfield/feedfield.install,v
retrieving revision 1.1
diff -u -p -r1.1 feedfield.install
--- feedfield.install	30 Nov 2006 14:48:13 -0000	1.1
+++ feedfield.install	5 Jan 2010 23:19:27 -0000
@@ -1,48 +1,37 @@
 <?php
-// $Id: feedfield.install,v 1.1 2006/11/30 14:48:13 thegreenman Exp $
+
+// $Id$
 
 /**
+ * @file
  * Implementation of hook_install().
  */
 function feedfield_install() {
-  switch ($GLOBALS['db_type']) {
-    case 'mysql':
-    case 'mysqli':
-      db_query("CREATE TABLE {node_field_feedfield_data} (
-          nid int unsigned NOT NULL default '0',
-          vid int unsigned NOT NULL default '0',
-          field_name varchar(32) NOT NULL default '',
-          delta int unsigned NOT NULL default '0',
-		  field_title varchar(255) NOT NULL default '',
-          field_url varchar(255) NOT NULL default '',
-          field_fid int unsigned NOT NULL default '0',
-          field_type varchar(32) NOT NULL default '',
-          PRIMARY KEY  (vid,field_name,delta)
-        ) /*!40100 DEFAULT CHARACTER SET utf8 */;");
-      break;
-
-    case 'pgsql':
-      db_query("CREATE TABLE {node_field_feedfield_data} (
-          nid integer NOT NULL default '0' CHECK (nid >= 0),
-          vid integer NOT NULL default '0' CHECK (nid >= 0),
-          field_name varchar(32) NOT NULL default '',
-          delta integer NOT NULL default '0' CHECK (nid >= 0),
-          field_title varchar(255) NOT NULL default '',
-		  field_url varchar(255) NOT NULL default '',
-		  field_fid integer NOT NULL default '0' CHECK (nid >= 0),
-		  field_type varchar(32) NOT NULL default '',
-          PRIMARY KEY  (vid,field_name,delta)
-        )");
-      break;
-  }
+  drupal_load('module', 'content');
+  content_notify('install', 'feedfield');
+}
+
+/**
+* Implementation of hook_uninstall().
+*/
+function feedfield_uninstall() {
+  drupal_load('module', 'content');
+  content_notify('uninstall', 'feedfield');
+}
+
+/**
+* Implementation of hook_enable().
+*/
+function feedfield_enable() {
+  drupal_load('module', 'content');
+  content_notify('enable', 'feedfield');
+}
+
+/**
+* Implementation of hook_disable().
+*/
+function feedfield_disable() {
+  drupal_load('module', 'content');
+  content_notify('disable', 'feedfield');
 }
 
-function feedfield_update_1(){
-	$items[]=update_sql("ALTER TABLE {node_field_feedfield_data} ADD `field_type` VARCHAR( 32 ) NOT NULL default ''");
-	return $items;
-} 
-
-function feedfield_update_2(){
-	$items[]=update_sql("ALTER TABLE {node_field_feedfield_data} ADD field_title varchar(255) NOT NULL default ''");
-	return $items;
-} 
\ No newline at end of file
Index: feedfield.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feedfield/feedfield.module,v
retrieving revision 1.4
diff -u -p -r1.4 feedfield.module
--- feedfield.module	17 May 2007 14:27:32 -0000	1.4
+++ feedfield.module	5 Jan 2010 23:19:28 -0000
@@ -1,121 +1,217 @@
 <?php
-// $Id: feedfield.module,v 1.4 2007/05/17 14:27:32 thegreenman Exp $
+
+// $Id$
 
 /**
  * @file
- * Defines a field type that will integrate with the Aggregator module, adding and removing feeds.
- * 
- * Much code has been borrowed from the link module.
+ * Defines a field type that will integrate with the Aggregator module, adding
+ * and removing feeds.
+ *
  */
 
 if (!function_exists('aggregation_sync_feed')) {
-    // Thre aggregation API is shared with the feed_node module. 
-	include_once('aggregation_api.inc');
-}	
-
+  // The aggregation API WAS shared with the feed_node 5.x module.  Not sure if
+  // any 6.x modules use it?
+  include_once('aggregation_api.inc');
+}
 
+/**
+ * Implementation of hook_theme().
+ */
+function feedfield_theme() {
+  return array(
+      'feedfield' => array(
+        'arguments' => array('element' => NULL),
+        ),
+      'feedfield_formatter_default' => array(
+        'arguments' => array('element' => NULL),
+        ),
+      );
+}
 
 /**
  * Implementation of hook_field_info().
+ *
+ * Here we indicate that the content module will use its default
+ * handling for the view of this field.
+ *
+ * Callbacks can be omitted if default handing is used.
+ * They're included here just so this module can be used
+ * as an example for custom modules that might do things
+ * differently.
  */
 function feedfield_field_info() {
   return array(
-    'feedfield' => array('label' => 'Feed'),
-  );
+      'feedfield' => array(
+        'label' => t('Feed'),
+        'description' => t('Create aggregator feed'),
+        'callbacks' => array(
+          'tables' => CONTENT_CALLBACK_DEFAULT,
+          'arguments' => CONTENT_CALLBACK_DEFAULT,
+          ),
+        ),
+      );
+}
+
+/**
+ * Implementation of hook_field_settings().
+ */
+function feedfield_field_settings($op, $field) {
+  switch ($op) {
+    case 'form':
+      $form = array();
+      $form['categories_fieldset'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Force Categories'),
+        '#collapsible' => TRUE,
+        '#collapsed' => empty($field['force_categories'])
+      );
+
+      $form['categories_fieldset']['force_categories'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Force Feed Categories'),
+        '#default_value' => $field['force_categories'],
+        '#description' => t('If checked, users will not have the option to
+                             select the feed\'s categories, and it will automatically be put in the
+                             category chosen below.'),
+      );
+
+      $form['categories_fieldset']['categories'] = array(
+        '#type' => 'checkboxes',
+        '#options' => aggregation_get_all_feed_categories(),
+        '#default_value' => !empty($field['categories']) ?
+        $field['categories'] : array(NULL)
+      );
+      $form['refresh_fieldset'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Force Refesh Interval'),
+        '#collapsible' => TRUE,
+        '#collapsed' => empty($field['force_refresh'])
+      );
+
+      $form['refresh_fieldset']['force_refresh'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Force Feed Refresh Interval'),
+        '#default_value' => $field['force_refresh'],
+        '#description' => t('If checked, users will not have the option to
+                             select the feed\'s refresh interval, and it will
+                             automatically be set to the value chosen below.'),
+      );
+
+      $form['refresh_fieldset']['refresh'] = array(
+        '#type' => 'select',
+        '#options' => aggregation_get_allowed_refresh_intervals(),
+        '#default_value' => isset($field['refresh']) ?
+        $field['refresh'] : 3600
+      );
+
+      $form['force_title'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Force Feed Title To Node Title'),
+        '#default_value' => $field['force_title'],
+        '#description' => t('If checked, users will not have the option to
+                             select the feed\'s title, and it will
+                             automatically be set to the node\'s title.')
+      );
+      return $form;
+
+    case 'save':
+      return array('force_categories', 'categories', 'force_refresh',
+      'refresh', 'force_title');
+
+    case 'database columns':
+      return array(
+          /* Only need to store fid, everything else will be stored by Aggregator.
+           * content_type_* will only serve as a junction table to link feeds
+           * to node versions.
+           */
+          'fid' => array('type' => 'int'),
+          );
+
+  }
 }
+
 /**
  * Implementation of hook_field().
  */
 function feedfield_field($op, &$node, $field, &$node_field, $teaser, $page) {
   switch ($op) {
-    case 'load':
-      switch($field['type']) {
-        case 'feedfield':
-          $result = db_query("SELECT field_url, field_fid, field_type,field_title FROM {node_field_feedfield_data} WHERE vid = %d AND field_name = '%s' ORDER BY delta", $node->vid, $field['field_name']);		  
-          while ($value = db_fetch_array($result)) {
-			// load the feed details from the aggregator in case they have been modified there
-            $feed=aggregation_get_feed_by_id($value['field_fid']);	
-            $values[] = array('value' => array(
-              'feed' => $feed['url'], 
-              'fid' => $value['field_fid'], 
-              'type'=>$value['field_type'], 
-              'title'=>$feed['title']));
-          }
-          $additions = array($field['field_name'] => $values);
-          break;
+
+    case 'presave':
+      foreach ($node_field as $delta => &$item) {
+        if(!empty($field['force_title'])) {
+          $item['title'] = $node->title;
+        }
       }
-      return $additions;
+      break;
 
-    case 'view':
-      foreach ($node_field as $delta => $item) {
-        $node_field[$delta]['view'] = _feedfield_field_view($field, $item['value'], $item, $node);
+    case 'load':
+      if ($field['type'] == 'feedfield') {
+        return _add_values_on_load($node_field, $field);
       }
-      return theme('field', $node, $field, $node_field, $teaser, $page);
+      break;
+
     case 'insert':
-      switch($field['type']) {        
-        case 'feedfield':          
-          foreach ($node_field as $delta => $item) {            
-            if ($item['value']['feed']) {
-              // we have a feed url 
-              // You might want to force specific titles, so we pass the title through a theme function
-              $title=theme('feedfield_title',$node,$field,$item);
-       		  
-              if ($fid=aggregation_sync_feed($title, $item['value']['feed'])) {
-              	// set categories      
-              	  if (is_array($item['value']['category'])) {
-              	    foreach ($item['value']['category'] as $cid => $set) {
-              	      // convert categories from option list to value list
-              	  	  if ($set) $feed_categories[]=$cid;
-              	    }
-              	  }  
-              	  aggregation_set_feed_categories($fid,$feed_categories);
-              	 
-              	  // If we have a new feed ID, then we are going to need to remove the old feed
-              	  if (($item['value']['fid']) and ($item['value']['fid'] != $fid)) {
-					aggregation_remove_feed_items($item['value']['fid']);
-              		aggregation_delete_feed($item['value']['fid']);
-              	  }               	  
-              	}              
-            // and now insert into our own table   
-              db_query("INSERT INTO {node_field_feedfield_data} (nid, vid, field_name, delta, field_url,field_fid,field_type,field_title) VALUES (%d, %d, '%s', %d, '%s',%d,'%s','%s')", $node->nid, $node->vid, $field['field_name'], $delta, $item['value']['feed'],$fid,$item['value']['type'],$title);			                      
-            } elseif ($item['value']['fid']) {
-              // no feed, but a feed id, that means we have to remove this feed
-              aggregation_remove_feed_items($item['value']['fid']);
-              aggregation_delete_feed($item['value']['fid']);
+      if ($field['type'] == 'feedfield') {
+        foreach ($node_field as $delta => &$item) {
+          if (!empty($item['feed'])) {
+            $new_fid = aggregation_sync_feed($item['title'], $item['feed']);
+            // we have a feed url
+            if ($new_fid != -1) {
+              aggregation_set_refresh_interval($new_fid, $item['refresh']);
+              if (is_array($item['categories'])) {
+                _set_categories($item['categories'], $new_fid);
+              }
+              /* If we have a new feed ID, then we are going to need to remove
+               * the old feed
+               */
+              if (!is_null($item['fid']) && $item['fid'] != $new_fid) {
+                _remove_feed($item['fid']);
+              }
             }
+            // and now set the variable to have cck save it to the database.
+            $item['fid'] = $new_fid;
+          } elseif ($item['fid']) {
+            // no feed, but a feed id, that means we have to remove this feed
+            _remove_feed($item['fid']);
           }
-          break;
+        }
       }
-      return;
+      break;
 
     case 'update':
       // Delete and insert, rather than update, in case a field was added.
-      switch($field['type']) {
-        case 'feedfield':			
-          db_query("DELETE FROM {node_field_feedfield_data} WHERE vid = %d AND field_name = '%s'", $node->vid, $field['field_name']);		                
-          feedfield_field('insert', $node, $field, $node_field, $teaser, $page);
-          break;
+      if ($field['type'] == 'feedfield') {
+
+        feedfield_field('insert', $node, $field, $node_field, $teaser, $page);
       }
-      return;
+      break;
 
     case 'delete':
       // Delete using nid rather than vid to purge all revisions.
-      switch($field['type']) {
-        case 'feedfield':
-	      // remove all linked feeds 
-          foreach ($node_field as $delta => $item) {
-            if ($item['value']['fid']) {  
-               aggregation_remove_feed_items($item['value']['fid']);
-               aggregation_delete_feed($item['value']['fid']);               
-            }  
-          }                
-          db_query("DELETE FROM {node_field_feedfield_data} WHERE nid = %d AND field_name = '%s'", $node->nid, $field['field_name']);
-          break;
+      if ($field['type'] == 'feedfield') {
+
+        // remove all linked feeds
+        foreach ($node_field as $delta => $item) {
+          if ($item['value']['fid']) {
+            _remove_feed($item['value']['fid']);
+          }
+        }
       }
-      return;
+      break;
+
   }
 }
 
+/**
+ * Implementation of hook_content_is_empty().
+ */
+function feedfield_content_is_empty($item, $field) {
+  if (empty($item['feed'])) {
+    return TRUE;
+  }
+  return FALSE;
+}
 
 
 /**
@@ -123,328 +219,197 @@ function feedfield_field($op, &$node, $f
  */
 function feedfield_widget_info() {
   return array(
-    'feedfield' => array(
-      'label' => 'Aggregator Feed',
-      'field types' => array('feedfield'),
-    ),
-  );
+      'feedfield' => array(
+        'label' => 'Aggregator Feed',
+        'field types' => array('feedfield'),
+        'multiple values' => CONTENT_HANDLE_CORE,
+        'callbacks' => array(
+          'default value' => CONTENT_CALLBACK_DEFAULT,
+          ),
+        ),
+      );
 }
 
 /**
  * Implementation of hook_widget().
  */
-function feedfield_widget($op, &$node, $field, &$node_field) {
-  switch ($op) {
-    case 'form':
-      $form = array();
-      $form[$field['field_name']] = array('#tree' => TRUE,'#type'=>'fieldset','#title'=>$field['widget']['label']);
+function feedfield_widget(&$form, &$form_state, $field, $items, $delta = 0) {
 
-      if ($field['multiple']) {
-        // Generate more fields if necessary on preview
-        if ($_POST['edit'][$field['field_name']]) {
-          $node_field = $_POST['edit'][$field['field_name']];
-        }
-        $delta = 0;
-        // Render feedfield fields for all the entered values
-        foreach ($node_field as $data) {
-        if ($data['value']['feed']) {
-            $form[$field['field_name']][$delta]=array('#tree' => TRUE,'#type'=>'fieldset');
-            _feedfield_widget_form($form[$field['field_name']][$delta], $field, $data, $delta);
-            
-            $delta++;
-          }
-        }
-        // Render two additional new feedfield fields
-        foreach (range($delta, $delta + 1) as $delta) {
-          $form[$field['field_name']][$delta]=array('#tree' => TRUE,'#type'=>'fieldset');
-          _feedfield_widget_form($form[$field['field_name']][$delta], $field, $node_field, $delta);
-        }
-      } // end if multiple
-      else {
-        // Convert an old 'single' value field into the new array based format
-        if ($node_field['value']) {
-          $node_field[0]['value'] = $node_field['value'];
-          unset($node_field['value']);
-        }
-        _feedfield_widget_form($form[$field['field_name']][0], $field, $node_field[0]);
-      }
-          
-      return $form;
+  $element = array(
+      '#type' => $field['widget']['type'],
+      '#default_value' => isset($items[$delta]) ? $items[$delta] : '',
+      );
+  return $element;
+}
 
-    case 'validate':            
-      foreach($node_field as $delta => $value) {
-        if ($value['value']['feed']) {                  
-          // Validate the feedfield
-          if (!feedfield_validate_link($value['value']['feed'])) {
-            form_set_error($field['field_name'] .']['. $delta. '][value][feed', t('Not a valid URL.'));
-          } elseif ($existing_feed=aggregation_get_feed_by_url($value['value']['feed'])) {
-            //url's must be unique
-          	if ($value['value']['fid']!=$existing_feed['fid']) {
-              form_set_error($field['field_name'] .']['. $delta. '][value][feed', t('This feed already exists in the aggregator.'));          
-            }
-          } elseif ($existing_feed=aggregation_get_feed_by_title($value['value']['title'])){
-            // Titles must be unique
-            if ($value['value']['fid']!=$existing_feed['fid']) {
-              form_set_error($field['field_name'] .']['. $delta. '][value][title', t('A feed already exists with this title.'));          
-            }            
-          }           
-        }  
-      }
-      return;
-      
-    case 'process form values':
-      foreach($node_field as $delta => $value) {
-        //_feedfield_widget_process($node_field[$delta],$delta);
-      }
-      return;
-    
-    case 'submit':
-      return;
-  }
+/**
+ * Implementation of FAPI hook_elements().
+ */
+function feedfield_elements() {
+  return array(
+      'feedfield' => array(
+        '#input' => TRUE,
+        '#columns' => array('title', 'feed', 'categories', 'refresh', 'fid'),
+        '#process' => array('feedfield_process'),
+        ),
+      );
 }
 
+/**
+ * Process the link type element before displaying the field.
+ */
+function feedfield_process($element, $edit, $form_state, $form) {
+  $field = $form['#field_info'][$element['#field_name']];
+  $delta = $element['#delta'];
+
+  if(empty($field['force_title'])) {
+    $element['title'] = array(
+        '#type' => 'textfield',
+        '#maxlength' => '255',
+        '#title' => t('Feed title'),
+        '#default_value' => isset($element['#value']['value']['title']) ?
+        $element['#value']['value']['title'] : $element['#value']['title']
+        );
+  } else {
+    $element['title'] = array(
+        '#type' => 'value',
+        '#value' => isset($element['#value']['value']['fid']) ?
+        $element['#value']['value']['fid'] : NULL
+        );
+  }
 
+  $element['feed'] = array(
+      '#type' => 'textfield',
+      '#maxlength' => '255',
+      '#title' => t(' Feed URL'),
+      '#default_value' => isset($element['#value']['value']['feed']) ?
+      $element['#value']['value']['feed'] : $element['#value']['feed'],
+      '#required' => ($delta == 0) ? $field['required'] : FALSE,
+      );
+
+  if(empty($field['force_refresh'])) {
+    $element['refresh'] = array(
+        '#type' => 'select',
+        '#title' => t('Update interval'),
+        '#default_value' => isset($element['#value']['value']['refresh']) ?
+        $element['#value']['value']['refresh'] :
+        (isset($element['#value']['refresh']) ? $element['#value']['refresh'] :
+         3600),
+        '#options' => aggregation_get_allowed_refresh_intervals()
+        );
+  } else {
+    $element['refresh'] = array(
+        '#type' => 'value',
+        '#value' => $field['refresh']
+        );
+  }
 
+  if(empty($field['force_categories'])) {
+    $element['categories'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Aggregator Categories'),
+        '#options' => aggregation_get_all_feed_categories(),
+        '#default_value' => !empty($element['#value']['value']['categories']) ?
+        $element['#value']['value']['categories'] : 
+        (is_array($element['#value']['categories']) ? $element['#value']['categories'] :
+         array(NULL))
+        );
+  } else {
+    $element['categories'] = array(
+        '#type' => 'value',
+        '#value' => $field['categories']
+        );
+  }
 
+  $element['fid'] = array(
+      '#type' => 'value',
+      '#value' => isset($element['#value']['value']['fid']) ?
+      $element['#value']['value']['fid'] : NULL
+      );
+  return $element;
+} 
 /**
- * Helper function renders the feedfield widget in both single and multiple value cases.
+ * Implementation of hook_field_formatter_info().
  */
-
-function _feedfield_widget_form (&$form_item, $field, $node_field, $delta = 0) {
-  $form_item['value'] = array(
-    '#tree' => true,
-    '#weigth' => $field['widget']['weight'],
-  );
-  
-  $form_item['value']['title'] = array(
-    '#type' => 'textfield',
-    '#maxlength' => '255',
-    '#title' => t('Feed title'),
-    '#default_value' => $node_field['value']['title'],
-  );
-  
-  $options = array (
-    'news' => t('News/blog feed'),
-    'wiki' => t('Wiki recent changes'),
-  );
-      
-  $form_item['value']['type'] = array(
-    '#type' => 'select',
-    '#title' => t('Feed type'),
-    '#default_value' => isset($node_field['value']['type']) ? $node_field['value']['type'] : 'news',
-    '#options' => $options,
-  );
-  
-  $form_item['value']['feed'] = array(
-    '#type' => 'textfield',
-    '#maxlength' => '255',
-    '#title' => t(' Feed URL'),
-    '#default_value' => $node_field['value']['feed'],
-    '#required' => ($delta == 0) ? $field['required'] : FALSE,
-    '#description' => $field['widget']['description'],
-  );
-  
- 
-  $form_item['value']['category'] = array(
-    '#type' => 'checkboxes',
-    '#title' => t('Aggregator Categories'),
-    '#options' => aggregation_get_all_feed_categories(),
-  );
-  ($node_field['value']['fid']) and $form_item['value']['category']['#default_value']=aggregation_get_assigned_feed_categories($node_field['value']['fid']);
-  
-  
-  $form_item['value']['fid'] = array(
-    '#type' => 'hidden',
-    '#value' => $node_field['value']['fid'],
-  );
-}
-
-/**
- * Implementation of hook_field_view() which performs any translation necessary.
- */
-function _feedfield_field_view($field, $value, $addlfields = array(), $node = NULL) {
-  $attributes = array();
-  $output = l(strlen($value['feed']) > 80 ? substr($value['feed'],0,80)."..." : $value['feed'],feedfield_cleanup_url($value['feed']),$attributes);
-  return $output;
-}
-
-/**
- * Implementation of hook_views_tables().
- */
-function feedfield_views_tables() {
-  $tables = array();
-
-  $fields = content_fields();
-  foreach ($fields as $field) {
-    if ($field['type'] == 'feedfield') {
-      $tables['node_field_feedfield_data_'. $field['field_name']] = array(
-        'name' => 'node_field_feedfield_data',
-        'join' => array(
-          'left' => array(
-            'table' => 'node',
-            'field' => 'vid',
-          ),
-          'right' => array(
-            'field' => 'vid',
-          ),
-            'extra' => array('field_name' => $field['field_name']),
-        ),
-        'fields' => array(
-          'field_url' => array(
-            'name' => 'Feed URL: '. $field['widget']['label'],
-            'sortable' => TRUE,
-          ),
-          'field_title' => array(
-            'name' => 'Feed title: '.$field['widget']['label'],
-            'sortable' => TRUE,
-          ),
-          'field_type' => array(
-            'name' => 'Feed type: '. $field['widget']['label'],
-            'sortable' => TRUE,
-          ),
-        ),
-        'sorts' => array(
-          'field_url' => array('name' => 'Feed URL: '. $field['widget']['label']),
-          'field_title' => array('name' => 'Feed title: '. $field['widget']['label']),
-          'field_type' => array('name' => 'Feed type: '. $field['widget']['label']),
-        ),
-        'filters' => array(
-          'field_url' => array(
-            'name' => 'Feed URL: '. $field['widget']['label'],
-            'operator' => array(
-              '='  => 'is',
-              'contains' => 'contains',
-              'begins' => 'begins with',
-              'ends' => 'ends wth',
-              'LIKE'  => 'matches pattern',
-            ),
-            'operator-handler' => '_text_filter_operator',
-          ),
-          'field_title' => array(
-            'name' => 'Feed title: '. $field['widget']['label'],
-            'operator' => array(
-              '='  => 'is',
-              'contains' => 'contains',
-              'begins' => 'begins with',
-              'ends' => 'ends wth',
-              'LIKE'  => 'matches pattern',
-            ),
-            'operator-handler' => '_text_filter_operator',
-          ),
-          'field_type' => array(
-            'name' => 'Feed type: '. $field['widget']['label'],
-            'operator' => "views_handler_operator_eqneq",
-            'list' => array('news'=>'news','wiki'=>'wiki'),
-          ),
+function feedfield_field_formatter_info() {
+  return array(
+      'default' => array(
+        'label' => t('Display Aggregator Block (default)'),
+        'field types' => array('feedfield'),
+        'multiple values' => CONTENT_HANDLE_CORE,
         ),
       );
-    }
-  }
-
-  return $tables;
 }
 
 /**
- * Forms a valid URL if possible from an entered address.
- * Trims whitespace and automatically adds an http:// to addresses without a protocol specified
- *
- * @param string $url
- * @param string $protocol The protocol to be prepended to the url if one is not specified
+ * FAPI theme for an individual text elements.
  */
-function feedfield_cleanup_url ($url, $protocol = "http") {
-  $url = trim($url);
-  
-  // Check if there is no protocol specified
-  $protocol_match = preg_match("/^([a-z0-9][a-z0-9\.\-_]*:\/\/)/i",$url);
-  if (empty($protocol_match)) {
-    // But should it be? Add an automatic http:// if it starts with a domain name
-    $domain_match = preg_match('/^(([a-z0-9]([a-z0-9\-_]*\.)+)(aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|[a-z]{2}))/i',$url);
-    if (!empty($domain_match)) {
-      $url = $protocol."://".$url;
-    }
-  }
-  
-  return $url;
+function theme_feedfield($element) {
+  return $element['#children'];
 }
 
 /**
- * A lenient verification for URLs. Accepts all URLs following RFC 1738 standard for URL formation.  
- *
- * @param string $text
- * @return mixed Returns boolean FALSE if the URL is not valid. On success, returns an object with
- * the following attributes: protocol, hostname, ip, and port.
- */
-function feedfield_validate_link($text) {
-  if (!preg_match(
-    // protocol
-    '/^([a-z0-9][a-z0-9\.\-_]*:\/\/)?'.
-  '('.
-    // domains
-      '(([a-z0-9]([a-z0-9\-_]*\.)+)(aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|[a-z]{2}))'.
-      // OR ip addresses
-      '|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'.
-  ')'.
-    // port number
-    '(:([0-9]{1,4}))?'.
-    // the rest of the path
-    "(\/[a-z0-9_\-\.~+%=&,$'():;*@]+)*".
-    // anchors
-    "\/?#?[a-z0-9_\-\.~+%=&,$'():;*@]*".
-    // the query string
-    "(\/?\?[a-z0-9+_\-\.\/%=&,$'():;*@]*)?".
-    // forward slash 0 or 1 times
-    '(\/)?'.
-    // end of the expression, case insensitive
-    '$/i', $text, $m)) {
-    return false;
-  }
-  else {
-    $url = new stdClass();
-    $url->protocol = $m[2];
-    $url->hostname = strtolower($m[5]).strtolower($m[7]);
-    $url->ip = $m[8];
-    $url->port = $m[10];
-    return $url;
-  }
+ * Theme function for default formatter.  This is just a quick and dirty way of
+ * showing feeds with the existing aggregator_block method.
+ */
+function theme_feedfield_formatter_default($element) {
+  $fid = 'feed-'.$element['#item']['value']['fid'];
+  $show = aggregator_block('view', $fid);
+  return $show['content'];
 }
 
 
+/////// "private" helper functions ////////
+
 /**
- * Implementation of hook_block.
- * 
+ * Helper function to add values to the node when the node is loaded.  Gets the values from the
+ * aggregator tables.
+ *
+ * @param $node_field Array of field items.
  */
-function feedfield_block($op = 'list', $delta = 0, $edit = array()) {
-  switch ($op){
-    case 'list':
-      $fields=content_fields();
-      if (is_array($fields)) {
-        foreach($fields as $field) {
-          if ($field['type']=='feedfield') {
-            $blocks[$field['field_name']]['info']=t("Feed for current node's @name feedfield",array('@name'=>$field['field_name']));
-          }
-       }
-     }    
-     return $blocks;
-   break;
-  case 'view':
-    if (arg(0)=='node' && is_numeric(arg(1))) {
-      $node=node_load(arg(1));
-      $feeds=$node->$delta;
-      return aggregator_block('view', 'feed-'.$feeds[0]['value']['fid']);      
-    }
-    break;  
+function _add_values_on_load($node_field, $field) {
+  foreach ($node_field as $key => $item) {
+    $feed=aggregation_get_feed_by_id($node_field[$key]['fid']);
+    $categories =
+      aggregation_get_assigned_feed_categories($node_field[$key]['fid']);
+    $values[] = array(
+        'value' => array(
+          'title'=> $feed['title'],
+          'feed' => $feed['url'],
+          'refresh' => $feed['refresh'],
+          'categories' => $categories,
+          'fid' => $feed['fid']
+          )
+        );
   }
+
+  $additions = array($field['field_name'] => $values);
+
+  return $additions;
 }
 
+/**
+ * Helper function to set a feed's categories.
+ *
+ * @param $categories Array of category cid's the feed should be
+ * associated with.
+ * @param $fid The feed's fid that should be associated with the categories.
+ */
+function _set_categories($categories, $fid) {
+  foreach ($categories as $cid => $set) {
+    // convert categories from option list to value list
+    if ($set) $feed_categories[]=$cid;
+  }
+  aggregation_set_feed_categories($new_fid, $feed_categories);
+}
 
 /**
- * Theme the way a feed title is displayed 
+ * Just removes all feed items of a feed, then deletes the feed.
  *
- * @return string with formatted title
+ * @param The feed that will be deleted.
  */
-function theme_feedfield_title($node,$field,$item) {
-	if (!$result=$item['value']['title']) {
-	  $result= $node->title.' ('.$item['value']['type'].')';
-	} 
-	return $result;  
-}
\ No newline at end of file
+function _remove_feed($fid) {
+  aggregation_remove_feed_items($fid);
+  aggregation_delete_feed($fid);
+}
+
Index: feedfield.test
===================================================================
RCS file: feedfield.test
diff -N feedfield.test
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ feedfield.test	5 Jan 2010 23:19:28 -0000
@@ -0,0 +1,136 @@
+<?php
+require_once(drupal_get_path('module', 'content') .'/tests/content.crud.test');
+
+class AggregationAPITestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'aggregation_api',
+      'description' => 'Tests the aggregation_api file.  Ensures that all
+      functions function correctly',
+      'group' => 'FeedField',
+    );
+  }
+
+  public function setUp() {
+    parent::setUp('aggregator');  // Enable any modules required for the test
+    // include the aggregation_api file
+    include_once('aggregation_api.inc');
+    // Create and log in our user
+    $privileged_user = $this->drupalCreateUser(array('access news feeds', 'administer news feeds'));
+    $this->drupalLogin($privileged_user);
+  }
+
+  /**
+   * Tests aggregation_api's aggregation_sync_feed function and helper
+   * functions.
+   */
+  public function testAggregationSyncFeed() {
+    //Add a feed using aggregation's form
+    $url = 'http://feeds.wired.com/wired/index'; 
+    $title = 'newFeed';
+
+    // Feed doesn't exist, so it should be inserted
+    $feedId = aggregation_sync_feed($title, $url);
+    // Function should return the fid of the feed that was just inserted
+    $this->assertNotNull($feedId);
+    // Function should not return -1.  -1 means there was a conflict
+    $this->assertNotEqual($feedId, -1);
+    $feedId2 = aggregation_sync_feed($title, $url);
+    // Feed exists.  Function should have returned the same fid
+    $this->assertEqual($feedId, $feedId2);
+    $title2 = $this->randomName(8);
+    $feedId = aggregation_sync_feed($title2, $url);
+    // Should have just updated the feed, so the feedId's should be the same
+    $this->assertEqual($feedId, $feedId2);
+    // Ensures that the name was changed
+    $this->assertEqual($feedId2, aggregation_sync_feed($title2, $url));
+    $url2 = 'http://rss.slashdot.org/slashdot/eqWf';
+    $feedId2 = aggregation_sync_feed($title2, $url2);
+    // Should have just updated the feed, so the feedId's should be the same
+    $this->assertEqual($feedId, $feedId2);
+    // Ensures that the name was changed
+    $this->assertEqual($feedId2, aggregation_sync_feed($title, $url2));
+  }
+
+  /**
+   * Tests aggregation_api's catogory functions.
+   */
+  public function testAggregationCatagory() {
+    $edit = array();
+    $edit['title'] = 'category1';
+    $edit['description'] = $this->randomName(50); 
+
+    $this->drupalPost('admin/content/aggregator/add/category', $edit, t('Save'));
+    $this->assertText(t('The category @title has been added.', array('@title' => $edit['title'])));
+
+    $edit['title'] = 'category2';
+    $edit['description'] = $this->randomName(50); 
+
+    $this->drupalPost('admin/content/aggregator/add/category', $edit, t('Save'));
+    $this->assertText(t('The category @title has been added.', array('@title' => $edit['title'])));
+
+    $edit['title'] = 'category3';
+    $edit['description'] = $this->randomName(50); 
+
+    $this->drupalPost('admin/content/aggregator/add/category', $edit, t('Save'));
+    $this->assertText(t('The category @title has been added.', array('@title' => $edit['title'])));
+
+    $categories = aggregation_get_all_feed_categories();
+    $ctr = 1;
+    foreach ($categories as $cid => $title)
+    {
+      $this->assertEqual($title, 'category'.$ctr);
+      ++$ctr;
+    }
+
+    $url = 'http://feeds.wired.com/wired/index'; 
+    $title = 'newFeed';
+
+    $fid = aggregation_sync_feed($title, $url);
+
+    $tmpCategories = $categories;
+    array_pop($tmpCategories);
+    aggregation_set_feed_categories($fid, array_keys($tmpCategories));
+    $this->assertEqual(aggregation_get_assigned_feed_categories($fid), array_keys($tmpCategories));
+
+    $url2 = 'http://rss.slashdot.org/slashdot/eqWf';
+    $title2 = 'newerFeed';
+
+    $fid2 = aggregation_sync_feed($title2, $url2);
+
+    aggregation_set_feed_categories($fid2, array_keys($categories));
+    $this->assertEqual(aggregation_get_assigned_feed_categories($fid2), array_keys($categories));
+    $this->assertEqual(aggregation_get_assigned_feed_categories($fid), array_keys($tmpCategories));
+
+    $this->assertEqual(aggregation_get_all_feed_categories(), $categories);
+
+  }
+
+   /**
+   * Tests aggregation_api's refresh functions.
+   * aggregation_get_allowed_refresh_intervals is left untested due to the
+   * simplicity of that function.
+   */
+  public function testAggregationRefresh() {
+    $url = 'http://rss.cnn.com/rss/cnn_mostpopular.rss';
+    $title = 'newFeed';
+    $fid = aggregation_sync_feed($title, $url);
+
+    $url2 = 'http://online.wsj.com/xml/rss/3_7011.xml';
+    $title2 = 'newFeed2';
+    $fid2 = aggregation_sync_feed($title2, $url2);
+
+    aggregation_set_refresh_interval($fid, 10800);
+    aggregation_set_refresh_interval($fid2, 64800);
+
+    $feed = aggregation_get_feed_by_id($fid);
+    $feed2 = aggregation_get_feed_by_id($fid2);
+
+    $this->assertEqual($feed['refresh'], 10800);
+    $this->assertEqual($feed2['refresh'], 64800);
+  }
+
+}
+
+
