diff --git a/feeds.module b/feeds.module index a73e061..b0a4225 100644 --- a/feeds.module +++ b/feeds.module @@ -666,6 +666,110 @@ function feeds_export($importer_id, $indent = '') { } /** + * Implementation of hook_feeds_after_parse + * + * Find the pre-existing nodes associated with this feed after parsing a new import (feeds_node_item table hasn't been updated) + */ +function feeds_feeds_after_parse(FeedsImporter $importer, FeedsSource $source) { + global $existingNodes; + $existingNodes = $importer->processor->getFeedNids($source); +} + +/** + * Implementation of hook_feeds_after_import + * + * Unpublish/Remove nodes no longer in feed. + */ +function feeds_feeds_after_import(FeedsImporter $importer, FeedsSource $source) { + // Checks + // Function does not exist then we are not in a node processor so drop out. + if (method_exists($importer->processor,"getFeedNids") == false) { + return; + } + // Make sure we have the processed nids. + if (!is_array($importer->processor->processed_nids)) { + return; + } + // Property determining the type of update does not exist drop out. + if (isset($importer->processor->config['update_non_existant']) == false) { + return; + } + // If none of the avaliable actions are defined drop out. + if (defined('FEEDS_SKIP_NON_EXISTANT') == false && defined('FEEDS_UNPUBLISH_NON_EXISTANT') == false && defined('FEEDS_REMOVE_NON_EXISTANT') == false) { + return; + } + + // Find nodes to be removed + // Pre-existing nodes: look up from database before the import (after_parse hook) + global $existingNodes; + if (!is_array($existingNodes)) return; + // Incoming feed nodes: as each item is processed, we identify its existing nid + $feedNodes = array(); + foreach ($importer->processor->processed_nids as $nid) { + $feedNodes[$nid] = $nid; + } + // Check if each existing node exists in the incoming feed + // If not, mark for removal + $removeNodes = array(); + foreach ($existingNodes as $nid) { + if (!array_key_exists($nid, $feedNodes)) { + $removeNodes[$nid] = $nid; + } + } + + // Remove nodes marked for removal + // If we have nothing to unpublish/remove then drop out. + if (count($removeNodes) == 0) { + return; + } + // Work out what we are going to do with the nodes not in feed. + switch ($importer->processor->config['update_non_existant']) { + case FEEDS_UNPUBLISH_NON_EXISTANT: + feeds_after_import_unpublish($removeNodes); + break; + case FEEDS_REMOVE_NON_EXISTANT: + feeds_after_import_remove($removeNodes); + break; + case FEEDS_SKIP_NON_EXISTANT: + default: + break; + } +} + +/** + * Unpublishes an array of node ids. + * + * @param $feedNodes array + * An array containing node ids. + */ +function feeds_after_import_unpublish($feedNodes) { + $counter = 0; + foreach ($feedNodes as $nid) { + $node = node_load($nid); + if ($node->status == 0) { + continue; + } + node_unpublish_action($node); + node_save($node); + $counter++; + } + drupal_set_message($counter . " node(s) unpublished"); +} + +/** + * Removes an array of node ids. + * + * @param $feedNodes array + * An array containing node ids. + */ +function feeds_after_import_remove($feedNodes) { + foreach ($feedNodes as $nid) { + node_delete($nid); + } + drupal_set_message(count($feedNodes) . " node(s) removed"); +} + +/** * Logs to a file like /mytmp/feeds_my_domain_org.log in temporary directory. */ function feeds_dbg($msg) { diff --git a/plugins/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc index 5ccea8e..157664b 100644 --- a/plugins/FeedsNodeProcessor.inc +++ b/plugins/FeedsNodeProcessor.inc @@ -19,6 +19,8 @@ define('FEEDS_NODE_UPDATE_EXISTING', 2); */ class FeedsNodeProcessor extends FeedsProcessor { + protected $processed_nids = array(); + /** * Implementation of FeedsProcessor::process(). */ @@ -34,6 +36,11 @@ class FeedsNodeProcessor extends FeedsProcessor { while ($item = $batch->shiftItem()) { // Create/update if item does not exist or update existing is enabled. + $nid = $this->existingItemId($batch, $source); + // Add the nid to the processed nids list. + if ($nid != 0) { + $this->processed_nids[$nid] = $nid; + } if (!($nid = $this->existingItemId($batch, $source)) || ($this->config['update_existing'] != FEEDS_SKIP_EXISTING)) { // Only proceed if item has actually changed. $hash = $this->hash($item); @@ -160,6 +167,7 @@ class FeedsNodeProcessor extends FeedsProcessor { 'content_type' => $type, 'input_format' => FILTER_FORMAT_DEFAULT, 'update_existing' => FEEDS_SKIP_EXISTING, + 'update_non_existant' => FEEDS_SKIP_NON_EXISTANT, 'expire' => FEEDS_EXPIRE_NEVER, 'mappings' => array(), 'author' => 0, @@ -232,6 +240,17 @@ class FeedsNodeProcessor extends FeedsProcessor { '#description' => t('Force update of items even if item source data did not change.'), '#default_value' => $this->config['skip_hash_check'], ); + $form['update_non_existant'] = array( + '#type' => 'radios', + '#title' => t('Update nodes missing in the feed'), + '#description' => t('Select how nodes missing in the feed should be updated.'), + '#options' => array( + FEEDS_SKIP_NON_EXISTANT => 'Skip non existant nodes', + FEEDS_UNPUBLISH_NON_EXISTANT => 'Unpublish non existant nodes', + FEEDS_REMOVE_NON_EXISTANT => 'Remove non existant nodes', + ), + '#default_value' => $this->config['update_non_existant'], + ); return $form; } @@ -382,6 +401,32 @@ class FeedsNodeProcessor extends FeedsProcessor { } /** + * Returns a list of nid's that originated from the FeedSource. + * + * @param $source object + * Feed srouce object. + * + * @return array + * Node ids. + */ + public function getFeedNids(FeedsSource $source) { + $nids = array(); + // Pull out nids + // From a node based feed + if ($source->feed_nid > 0) { + $results = db_query("SELECT nid FROM {feeds_node_item} WHERE feed_nid = %d", $source->feed_nid); + } + // From an import form feed + else { + $results = db_query("SELECT nid FROM {feeds_node_item} WHERE id = '%s'", $source->id); + } + while ($result = db_fetch_array($results)) { + $nids[$result['nid']] = $result['nid']; + } + return $nids; + } + + /** * Creates a new node object in memory and returns it. */ protected function buildNode($nid, $feed_nid) { diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index 1bd8904..471a863 100644 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -5,6 +5,10 @@ define('FEEDS_SKIP_EXISTING', 0); define('FEEDS_REPLACE_EXISTING', 1); define('FEEDS_UPDATE_EXISTING', 2); +// Update mode for non-existant items. +define('FEEDS_SKIP_NON_EXISTANT', 3); +define('FEEDS_UNPUBLISH_NON_EXISTANT', 4); +define('FEEDS_REMOVE_NON_EXISTANT', 5); /** * Abstract class, defines interface for processors.