FeedsAtomRDFProcessor.inc in Feeds Atom 6
Same filename and directory in other branches
Contains the feeds atom RDF processor class.
File
plugins/FeedsAtomRDFProcessor.incView source
<?php
/**
* @file
* Contains the feeds atom RDF processor class.
*/
/**
* Creates nodes from feed items.
*/
class FeedsAtomRDFProcessor extends FeedsNodeProcessor {
/**
* Implementation of FeedsProcessor::process().
*/
public function process(FeedsImportBatch $batch, FeedsSource $source) {
// Keep track of processed items in this pass, set total number of items.
$processed = 0;
if (!$batch
->getTotal(FEEDS_PROCESSING)) {
$batch
->setTotal(FEEDS_PROCESSING, $batch
->getItemCount());
// These two calls to setTotal() were added as a workaround to problems
// upstream in Feeds. See http://drupal.org/node/1139376
$batch
->setTotal(FEEDS_FETCHING, $batch
->getProgress(FEEDS_FETCHING));
$batch
->setTotal(FEEDS_PARSING, $batch
->getProgress(FEEDS_PARSING));
}
// These variables are set outsite of the while loop to reduce the total
// number of function calls.
$feeds_node_batch_size = variable_get('feeds_node_batch_size', FEEDS_NODE_BATCH_SIZE);
$batch_total = $batch
->getTotal(FEEDS_PROCESSING);
while ($item = $batch
->shiftItem()) {
// If the item already exists and we're flagged to delete it, do that instead.
// If the item doesn't already exists and we're flagged to delete it, do nothing.
// This part is added from the parent class.
if (!empty($item['deleted'])) {
$nid = $this
->existingItemIdGlobal($batch, $source);
if (!empty($nid)) {
node_delete($nid);
}
continue;
}
// Create/update if item does not exist or update existing is enabled.
if (!($nid = $this
->existingItemId($batch, $source)) || $this->config['update_existing'] != FEEDS_SKIP_EXISTING) {
// Only proceed if item has actually changed.
$hash = $this
->hash($item);
if (!empty($nid) && $hash == $this
->getHash($nid)) {
continue;
}
$node = $this
->buildNode($nid, $source->feed_nid);
$node->feeds_node_item->hash = $hash;
// Map and save node. If errors occur don't stop but report them.
try {
$this
->map($batch, $node, $source);
node_save($node);
if (!empty($nid)) {
$batch->updated++;
}
else {
$batch->created++;
}
} catch (Exception $e) {
drupal_set_message($e
->getMessage(), 'warning');
watchdog('feeds', $e
->getMessage(), array(), WATCHDOG_WARNING);
}
}
$processed++;
// setProgress() does not handle well the case in which the feeds_node_batch_size
// is exactly equal to the number of incoming items.
// This first if statement is a workaround for that bug with setProgress.
if ($processed == $batch_total && $processed == $feeds_node_batch_size) {
// Explicitly set the progress to FEED_BATCH_COMPLETE rather than calculating
// the value as the next 'else if' will do.
$batch
->setProgress(FEEDS_PROCESSING, FEEDS_BATCH_COMPLETE);
return;
}
else {
if ($processed >= $feeds_node_batch_size) {
$batch
->setProgress(FEEDS_PROCESSING, $batch->created + $batch->updated);
return;
}
}
}
// Set messages.
if ($batch->created) {
drupal_set_message(format_plural($batch->created, 'Created @number @type node.', 'Created @number @type nodes.', array(
'@number' => $batch->created,
'@type' => node_get_types('name', $this->config['content_type']),
)));
}
elseif ($batch->updated) {
drupal_set_message(format_plural($batch->updated, 'Updated @number @type node.', 'Updated @number @type nodes.', array(
'@number' => $batch->updated,
'@type' => node_get_types('name', $this->config['content_type']),
)));
}
else {
drupal_set_message(t('There is no new content.'));
}
$batch
->setProgress(FEEDS_PROCESSING, FEEDS_BATCH_COMPLETE);
}
/**
* Add handler to find an id globally
*
* Because our deletion feeds are not necessarily the same feed as the "create new"
* feed, we need to check for existing items across all feeds, not just
* the current feed. As long as the GUID is really unique that should not
* cause a problem.
*
* @todo Refactor this once http://drupal.org/node/828176 gets resolved.
*
* @see FeedsNodeProcessor
*/
protected function existingItemIdGlobal($source_item, FeedsSource $source) {
// Iterate through all unique targets and test whether they do already
// exist in the database.
foreach ($this
->uniqueTargets($source_item) as $target => $value) {
switch ($target) {
case 'url':
$nid = db_result(db_query("SELECT nid FROM {feeds_node_item} WHERE id = '%s' AND url = '%s'", $source->id, $value));
break;
case 'guid':
$nid = db_result(db_query("SELECT nid FROM {feeds_node_item} WHERE id = '%s' AND guid = '%s'", $source->id, $value));
break;
}
if ($nid) {
// Return with the first nid found.
return $nid;
}
}
return 0;
}
/**
* Override parent::map() to load all available add-on mappers.
*
* We also add a $source parameter that contains the FeedsSource object that
* controls this feed.
*/
protected function map(FeedsImportBatch $batch, $target_item = NULL, FeedsSource $source) {
// Static cache $targets as getMappingTargets() may be an expensive method.
static $sources;
if (!isset($sources[$this->id])) {
$sources[$this->id] = feeds_importer($this->id)->parser
->getMappingSources();
}
static $targets;
if (!isset($targets[$this->id])) {
$targets[$this->id] = $this
->getMappingTargets();
}
$parser = feeds_importer($this->id)->parser;
if (empty($target_item)) {
$target_item = array();
}
// Many mappers add to existing fields rather than replacing them. Hence we
// need to clear target elements of each item before mapping in case we are
// mapping on a prepopulated item such as an existing node.
if (is_array($target_item)) {
$target_item = (object) $target_item;
$convert_to_array = TRUE;
}
foreach ($this->config['mappings'] as $mapping) {
if (isset($targets[$mapping['target']]['real_target'])) {
unset($target_item->{$targets[$mapping['target']]['real_target']});
}
elseif (isset($target_item->{$mapping['target']})) {
unset($target_item->{$mapping['target']});
}
}
if ($convert_to_array) {
$target_item = (array) $target_item;
}
// Set custom fields
// http://groups.drupal.org/node/8796
// http://www.stonemind.net/blog/index.php?s=cck
// http://civicactions.com/blog/cck_import_and_update
// http://www.lullabot.com/articles/quick-and-dirty-cck-imports
$source_item = $batch
->currentItem();
foreach ($source_item['rdf'] as $key => $value) {
if (empty($key)) {
continue;
}
$value = $source_item['rdf'][$key];
$fname = drupal_substr($key, 0, 6);
if ($fname == "field_") {
// Build up a field value.
$target_item->{$key} = $value;
}
else {
// Set properties on the node. There's a couple we know we don't want,
// because their meaning is site-specific anyway.
// @todo Replace this logic with the mapping engine for more flexibility.
if (!in_array($key, array(
'nid',
'vid',
'revision_uid',
'log',
'created',
'changed',
'revision_timestamp',
'last_comment_timestamp',
))) {
$target_item->{$key} = $value;
}
}
}
// This is where the actual mapping happens: For every mapping we envoke
// the parser's getSourceElement() method to retrieve the value of the
// source element and pass it to the processor's setTargetElement() to stick
// it on the right place of the target item.
//
// If the mapping specifies a callback method, use the callback instead of
// setTargetElement().
self::loadMappers();
foreach ($this->config['mappings'] as $mapping) {
// Retrieve source element's value from parser.
if (is_array($sources[$this->id][$mapping['source']]) && isset($sources[$this->id][$mapping['source']]['callback']) && function_exists($sources[$this->id][$mapping['source']]['callback'])) {
$callback = $sources[$this->id][$mapping['source']]['callback'];
$value = $callback($batch, $mapping['source']);
}
else {
$value = $parser
->getSourceElement($batch, $mapping['source']);
}
// Map the source element's value to the target.
if (is_array($targets[$this->id][$mapping['target']]) && isset($targets[$this->id][$mapping['target']]['callback']) && function_exists($targets[$this->id][$mapping['target']]['callback'])) {
$callback = $targets[$this->id][$mapping['target']]['callback'];
$callback($target_item, $mapping['target'], $value);
}
else {
$this
->setTargetElement($target_item, $mapping['target'], $value);
}
}
// Allow other modules to add additional mapping
// Invokes hook_feeds_atom_rdf_map_alter().
drupal_alter('feeds_atom_rdf_map', $target_item, $source_item, $source);
return $target_item;
}
}
/**
* Specialized version of FeedsEnclosure to ensure uniqueness when saving.
*
*/
class FeedsEnclosureUnique extends FeedsEnclosure {
/**
* Saves the file represented by this enclosure to disk.
*
* If the file already exists, based on its sha1() hash, then we will simply
* reuse the existing file rather than saving a new one.
*
* @param $target_dir
* The directory to which to save the file. Note that if the file has already
* been imported it is possible that it will not be in the requested directory,
* in which case this method returns the existing file info in its existing
* location.
* @return
* The file info array as defined by filefield of the file that we just saved,
* or of the pre-existing file that should be used instead.
*/
public function saveTo($target_dir) {
$new_file_hash = sha1_file($this->file);
$fid = db_result(db_query("SELECT fid FROM {feeds_atom_file_import} WHERE sha1 = '%s'", $new_file_hash));
if ($fid) {
// Pull the info for the existing file and return that. We won't save
// the new file at all.
$info = field_file_load($fid);
return $info;
}
else {
// Save the new file, and record its hash for later matching.
$info = field_file_save_file($this->file, array(), $target_dir);
db_query("INSERT INTO {feeds_atom_file_import} (fid, sha1) VALUES (%d, '%s')", $info['fid'], $new_file_hash);
return $info;
}
}
}
Classes
Name | Description |
---|---|
FeedsAtomRDFProcessor | Creates nodes from feed items. |
FeedsEnclosureUnique | Specialized version of FeedsEnclosure to ensure uniqueness when saving. |