You are here

FeedsAtomRDFProcessor.inc in Feeds Atom 6

Same filename and directory in other branches
  1. 7 plugins/FeedsAtomRDFProcessor.inc

Contains the feeds atom RDF processor class.

File

plugins/FeedsAtomRDFProcessor.inc
View 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

Namesort descending Description
FeedsAtomRDFProcessor Creates nodes from feed items.
FeedsEnclosureUnique Specialized version of FeedsEnclosure to ensure uniqueness when saving.