diff --git a/includes/FeedsSource.inc b/includes/FeedsSource.inc index 98cd0f1..c8fc243 100644 --- a/includes/FeedsSource.inc +++ b/includes/FeedsSource.inc @@ -90,6 +90,11 @@ class FeedsState { public $failed; /** + * IDs of entities to be removed. + */ + public $removeList; + + /** * Constructor, initialize variables. */ public function __construct() { diff --git a/plugins/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc index 519dfc5..f10ba83 100644 --- a/plugins/FeedsNodeProcessor.inc +++ b/plugins/FeedsNodeProcessor.inc @@ -1,5 +1,7 @@ t('Select after how much time nodes should be deleted. The node\'s published date will be used for determining the node\'s age, see Mapping settings.'), '#default_value' => $this->config['expire'], ); + $form['update_non_existent']['#options'][FEEDS_UNPUBLISH_NON_EXISTENT] = t('Unpublish non existent nodes'); return $form; } @@ -378,4 +381,30 @@ class FeedsNodeProcessor extends FeedsProcessor { } return 0; } + + /** + * Overrides FeedsProcessor::clean() + * Allow unpublish instead of delete. + * + * @param FeedsState $state + */ + protected function clean(FeedsState $state) { + // Delegate to parent if not unpublishing or option not set + if (!isset($this->config['update_non_existent']) || $this->config['update_non_existent'] != FEEDS_UNPUBLISH_NON_EXISTENT) { + return parent::clean($state); + } + + $total = count($state->removeList); + if ($total) { + $nodes = node_load_multiple($state->removeList); + foreach ($nodes as &$node) { + if ($node->status == 0) { + continue; + } + node_unpublish_action($node); + node_save($node); + $state->updated++; + } + } + } } diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index c2b0af5..1c482e6 100755 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -9,6 +9,9 @@ define('FEEDS_SKIP_EXISTING', 0); define('FEEDS_REPLACE_EXISTING', 1); define('FEEDS_UPDATE_EXISTING', 2); +// Options for handling content in Drupal but not in source data. +define('FEEDS_SKIP_NON_EXISTENT', 'skip'); +define('FEEDS_DELETE_NON_EXISTENT', 'delete'); // Default limit for creating items on a page load, not respected by all // processors. @@ -176,11 +179,18 @@ abstract class FeedsProcessor extends FeedsPlugin { */ public function process(FeedsSource $source, FeedsParserResult $parser_result) { $state = $source->state(FEEDS_PROCESS); + if (!isset($state->removeList) && $parser_result->items) { + $this->initEntitiesToBeRemoved($source, $state); + } while ($item = $parser_result->shiftItem()) { // Check if this item already exists. $entity_id = $this->existingEntityId($source, $parser_result); + // If it's included in the feed, it must not be removed on clean. + if ($entity_id) { + unset($state->removeList[$entity_id]); + } $skip_existing = $this->config['update_existing'] == FEEDS_SKIP_EXISTING; module_invoke_all('feeds_before_update', $source, $item, $entity_id); @@ -264,6 +274,9 @@ abstract class FeedsProcessor extends FeedsPlugin { if ($source->progressImporting() != FEEDS_BATCH_COMPLETE) { return; } + // Remove not included items if needed. + // What actually happens to removed items depends on clean function. + $this->clean($state); $info = $this->entityInfo(); $tokens = array( '@entity' => strtolower($info['label']), @@ -290,6 +303,16 @@ abstract class FeedsProcessor extends FeedsPlugin { ), ); } + if ($state->deleted) { + $messages[] = array( + 'message' => format_plural( + $state->deleted, + 'Removed @number @entity.', + 'Removed @number @entities.', + array('@number' => $state->deleted) + $tokens + ), + ); + } if ($state->failed) { $messages[] = array( 'message' => format_plural( @@ -313,6 +336,59 @@ abstract class FeedsProcessor extends FeedsPlugin { } /** + * Initialize the array of entities to remove with all existing entities + * previously imported from the source. + * + * @param FeedsSource $source + * Source information about this import. + * @param FeedsState $state + */ + protected function initEntitiesToBeRemoved(FeedsSource $source, FeedsState $state) { + $state->removeList = array(); + // We fill it only if needed. + if (!isset($this->config['update_non_existent']) || $this->config['update_non_existent'] == FEEDS_SKIP_NON_EXISTENT) { + return; + } + // Build base select statement. + $info = $this->entityInfo(); + $select = db_select($info['base table'], 'e'); + $select->addField('e', $info['entity keys']['id'], 'entity_id'); + $select->join( + 'feeds_item', + 'fi', + "e.{$info['entity keys']['id']} = fi.entity_id AND fi.entity_type = '{$this->entityType()}'"); + $select->condition('fi.id', $this->id); + $select->condition('fi.feed_nid', $source->feed_nid); + $entities = $select->execute(); + // If not found on process, existing entities will be deleted. + foreach ($entities as $entity) { + // Obviously, items which are still included in the source feed will be + // removed from this array when processed. + $state->removeList[$entity->entity_id] = $entity->entity_id; + } + } + + /** + * Deletes entities which were not found on process. + * + * @todo batch delete? + * + * @param FeedsState $state + */ + protected function clean(FeedsState $state) { + // We clean only if needed. + if (!isset($this->config['update_non_existent']) || $this->config['update_non_existent'] == FEEDS_SKIP_NON_EXISTENT) { + return; + } + + $total = count($state->removeList); + if ($total) { + $this->entityDeleteMultiple($state->removeList); + $state->deleted += $total; + } + } + + /** * Remove all stored results or stored results up to a certain time for a * source. * @@ -538,6 +614,7 @@ abstract class FeedsProcessor extends FeedsPlugin { 'input_format' => NULL, 'skip_hash_check' => FALSE, 'bundle' => $bundle, + 'update_non_existent' => FEEDS_SKIP_NON_EXISTENT, ); } @@ -598,6 +675,17 @@ abstract class FeedsProcessor extends FeedsPlugin { '#required' => TRUE, ); + $form['update_non_existent'] = array( + '#type' => 'radios', + '#title' => t('Update entities missing in the feed'), + '#description' => t('Select how entities missing in the feed should be updated.'), + '#options' => array( + FEEDS_SKIP_NON_EXISTENT => 'Skip non existent entities', + FEEDS_DELETE_NON_EXISTENT => 'Delete non existent entities', + ), + '#default_value' => $this->config['update_non_existent'], + ); + return $form; }