You are here

feedapi_node.module in FeedAPI 6

Same filename and directory in other branches
  1. 5 feedapi_node/feedapi_node.module

Handle how the feed items are represented as a content Handle the processing of the feed items

File

feedapi_node/feedapi_node.module
View source
<?php

/**
 * @file
 * Handle how the feed items are represented as a content
 * Handle the processing of the feed items
 */

/**
 * Implementation of hook_help().
 */
function feedapi_node_help($path, $arg) {
  switch ($path) {
    case 'admin/help#feedapi_node':
      return t('Processor for FeedAPI, transforms items into nodes.');
    case 'feedapi/full_name':
      return t('FeedAPI Node - create nodes from feed items');
  }
}

/**
 * Implementation of hook_nodeapi().
 */
function feedapi_node_nodeapi(&$node, $op, $teaser) {
  switch ($op) {
    case 'load':
      $result = db_query('SELECT fi.*, ff.feed_nid FROM {feedapi_node_item} fi JOIN {feedapi_node_item_feed} ff ON fi.nid = ff.feed_item_nid WHERE fi.nid = %d', $node->nid);
      while ($f = db_fetch_object($result)) {
        $node->feedapi_node = $f;
        $feed_nids[$f->feed_nid] = $f->feed_nid;
      }
      if (isset($node->feedapi_node)) {
        $node->feedapi_node->feed_nids = $feed_nids;
        unset($node->feedapi_node->feed_nid);
      }
      break;
    case 'insert':
      if (isset($node->feedapi_node) && $node->feedapi_node->feed_item) {

        // Why do we stick the nid on the feed item here?
        $node->feedapi_node->feed_item->nid = $node->nid;
        foreach ($node->feedapi_node->feed_nids as $feed_nid) {
          db_query("INSERT INTO {feedapi_node_item_feed} (feed_nid, feed_item_nid) VALUES (%d, %d)", $feed_nid, $node->nid);
        }
        $feed_item = $node->feedapi_node->feed_item;
        $arrived = time();
        db_query("INSERT INTO {feedapi_node_item} (nid, url, timestamp, arrived, guid) VALUES (%d, '%s', %d, %d, '%s')", $node->nid, $feed_item->options->original_url, $feed_item->options->timestamp, $arrived, $feed_item->options->guid);

        // Construct $node->feedapi_node component.
        // This should look the same as when loaded from DB.
        $node->feedapi_node->url = $feed_item->options->original_url;
        $node->feedapi_node->guid = $feed_item->options->guid;
        $node->feedapi_node->arrived = $arrived;
        $node->feedapi_node->timestamp = $feed_item->options->timestamp;
        $node->feedapi_node->nid = $node->nid;
      }
      break;
    case 'update':
      if (isset($node->feedapi_node)) {
        if ($node->feedapi_node->feed_item) {
          $feed_item = $node->feedapi_node->feed_item;
          db_query("UPDATE {feedapi_node_item} SET url = '%s', timestamp = %d, guid = '%s' WHERE nid = %d", $feed_item->options->original_url, $feed_item->options->timestamp, $feed_item->options->guid, $node->nid);
        }
        db_query('DELETE FROM {feedapi_node_item_feed} WHERE feed_item_nid = %d', $node->nid);
        foreach ($node->feedapi_node->feed_nids as $feed_nid) {
          db_query("INSERT INTO {feedapi_node_item_feed} (feed_nid, feed_item_nid) VALUES (%d, %d)", $feed_nid, $node->nid);
        }
      }
      break;
    case 'delete':
      if (isset($node->feedapi_node)) {
        db_query('DELETE FROM {feedapi_node_item} WHERE nid = %d', $node->nid);
        db_query('DELETE FROM {feedapi_node_item_feed} WHERE feed_item_nid = %d', $node->nid);
      }
      if (isset($node->feed)) {
        db_query('DELETE FROM {feedapi_node_item_feed} WHERE feed_nid = %d', $node->nid);
      }
      break;
  }
}

/**
 * Implementation of hook_link().
 */
function feedapi_node_link($type, $node = NULL) {
  $links = array();
  if ($type == 'node') {
    if (isset($node->feed)) {
      if (count($node->feed->processors) > 0 && module_exists('views')) {
        if (in_array('feedapi_node', $node->feed->processors)) {
          $links['view_items'] = array(
            'title' => t('Feed items'),
            'href' => 'feed-item/' . $node->nid,
          );
        }
      }
    }
    if (isset($node->feedapi_node)) {
      $result = db_query(db_rewrite_sql("SELECT n.nid, n.title FROM {node} n WHERE n.nid IN (" . db_placeholders($node->feedapi_node->feed_nids, 'int') . ") ORDER BY title DESC"), $node->feedapi_node->feed_nids);
      $owner_feeds_num = count($node->feedapi_node->feed_nids);
      while ($feed = db_fetch_object($result)) {
        $links['feedapi_feed' . ($owner_feeds_num == 1 ? '' : '_' . $feed->nid)] = array(
          'title' => t('Feed:') . ' ' . $feed->title,
          'href' => 'node/' . $feed->nid,
        );
      }
      if ($node->feedapi_node->url) {
        $links['feedapi_original'] = array(
          'title' => t('Original article'),
          'href' => $node->feedapi_node->url,
        );
      }
    }
  }
  return $links;
}

/**
 * Implementation of hook_node_views().
 */
function feedapi_node_views_api() {
  return array(
    'api' => 2,
    'path' => drupal_get_path('module', 'feedapi_node') . '/views',
  );
}

/**
 * Implementation of hook_ctools_plugin_api().
 */
function feedapi_node_ctools_plugin_api($module, $api) {
  if ($module == 'feedapi_mapper' && $api == 'feedapi_mapper_default') {
    return array(
      'version' => 1,
    );
  }
}

/**
 * Implementation of hook_feedapi_mapper_default().
 * Provides default mapping for FeedAPI Mapper 2.x
 */
function feedapi_node_feedapi_mapper_default() {
  $feedapi_mapper = new stdClass();
  $feedapi_mapper->disabled = FALSE;
  $feedapi_mapper->api_version = 1;
  $feedapi_mapper->param = 'feed';
  $feedapi_mapper->mapping = array(
    'a:1:{i:0;s:11:"description";}' => 'a:2:{i:0;s:4:"node";i:1;s:4:"body";}',
    'a:1:{i:0;s:5:"title";}' => 'a:2:{i:0;s:4:"node";i:1;s:5:"title";}',
  );
  $feedapi_mapper->unique_elements = array(
    'a:1:{i:0;s:11:"description";}' => FALSE,
    'a:1:{i:0;s:5:"title";}' => FALSE,
  );
  return array(
    'feedapi_node' => $feedapi_mapper,
  );
}

/**
 * Implementation of hook_feedapi_settings_form().
 * If a module provides parsers and processors it MUST evaluate the $type variable
 * to return different forms for parsers and processors.
 * There might be a better term for parsers and processors than $type.
 */
function feedapi_node_feedapi_settings_form($type) {
  $form = array();
  switch ($type) {
    case 'processors':
      $ct_types = node_get_types();
      $ct_options = array(
        '' => t('Select type'),
      );
      if (is_array($ct_types)) {
        foreach ($ct_types as $key => $data) {
          if (!feedapi_enabled_type($key)) {
            $ct_options[$key] = $data->name;
          }
        }
      }
      $form['content_type'] = array(
        '#type' => 'select',
        '#title' => t('Node type of feed items'),
        '#default_value' => '',
        '#options' => $ct_options,
        '#description' => t('Choose the node type for feed item nodes created by this feed.'),
      );
      $format_options = array(
        FILTER_FORMAT_DEFAULT => t('Default format'),
      );
      $formats = filter_formats();
      foreach ($formats as $format) {
        $format_options[$format->format] = $format->name;
      }
      $form['input_format'] = array(
        '#type' => 'select',
        '#title' => t('Input format for feed items'),
        '#default_value' => FILTER_FORMAT_DEFAULT,
        '#options' => $format_options,
        '#description' => t('Choose the input format for feed item nodes created by this feed.'),
      );
      $form['node_date'] = array(
        '#type' => 'radios',
        '#title' => t('Created date of item nodes'),
        '#options' => array(
          'feed' => t('Retrieve from feed'),
          'current' => t('Use time of download'),
        ),
        '#default_value' => 'feed',
      );
      $form['x_dedupe'] = array(
        '#type' => 'radios',
        '#title' => t('Duplicates'),
        '#description' => t('If you choose "check for duplicates on all feeds", a feed item will not be created if it already exists on *ANY* feed. Instead, the existing feed item will be linked to the feed. If you are not sure, choose the first option.'),
        '#options' => array(
          0 => t('Check for duplicates only within feed'),
          1 => t('Check for duplicates on all feeds'),
        ),
        '#default_value' => 0,
      );
      break;
  }
  return $form;
}

/**
 * Implementation of hook_feedapi_item().
 */
function feedapi_node_feedapi_item($op) {
  switch ($op) {
    case 'type':
      return array(
        "XML feed",
      );
    case 'save':
    case 'expire':
    case 'update':
    case 'delete':
    case 'purge':
    case 'unique':
    default:
      if (function_exists('_feedapi_node_' . $op)) {
        $args = array_slice(func_get_args(), 1);
        return call_user_func_array('_feedapi_node_' . $op, $args);
      }
  }
}

/**
 * Check for expired items, pass them to the item_expire function
 *
 * @TO DO Add cron timeout checking here, there may be too many items (nodes) to delete
 *
 * We implement the same logic as a db query. The old code is
 *
 *     if (isset($item->arrived) || isset($item->timestamp)) {
 *       $diff = abs(time() - (isset($item->timestamp) ? $item->timestamp : $item->timestamp));
 *       if ($diff > $settings['items_delete']) {
 *       ................
 *       }
 *     }
 */
function _feedapi_node_expire($feed, $settings) {
  $count = 0;
  if ($settings['items_delete'] > FEEDAPI_NEVER_DELETE_OLD) {
    $timexpire = time() - $settings['items_delete'];

    // @ TODO Review this query conditions
    $result = db_query("SELECT * FROM {feedapi_node_item} fn JOIN {feedapi_node_item_feed} ff ON ff.feed_item_nid = fn.nid WHERE ff.feed_nid = %d AND ( (fn.timestamp > 0 AND fn.timestamp < %d) OR (fn.timestamp = 0 AND fn.arrived > 0 AND fn.arrived < %d) )", $feed->nid, $timexpire, $timexpire);
    while ($item = db_fetch_object($result)) {

      // We callback feedapi for deleting
      feedapi_expire_item($feed, $item);
      $count++;
    }
  }
  return $count;
}

/**
 * Create a node from the feed item
 * Store the relationship between the node and the feed item
 */
function _feedapi_node_save($feed_item, $feed_nid, $settings = array()) {

  // Avoid error message flood when creating tons of items.
  static $error_msg = FALSE;
  module_load_include('inc', 'node', 'node.pages');

  // Don't save anything if neither url nor guid given.
  if (!$feed_item->options->original_url) {
    if (!$feed_item->options->guid) {
      return $feed_item;
    }
  }

  // If there are dupes on other feeds, don't create new feed item, but link this feed
  // to existing feed item.
  // Heads up: if there is a duplicate on the SAME feed,
  // _feedapi_node_save() won't even be called.
  if (isset($feed_item->feedapi_node->duplicates)) {
    foreach ($feed_item->feedapi_node->duplicates as $fi_nid => $f_nids) {
      $feed_item_node = node_load($fi_nid);
      $feed_item_node->feedapi_node->feed_nids[$feed_nid] = $feed_nid;
      node_object_prepare($feed_item_node);
      node_save($feed_item_node);
    }

    //mark this item as updated.
    $feed_item->is_updated = TRUE;
    return FALSE;
  }

  // Constructs the node object.
  $node = new stdClass();
  if (isset($feed_item->nid)) {
    $node->nid = $feed_item->nid;
    $node->vid = db_result(db_query("SELECT vid FROM {node} WHERE nid = %d", $node->nid));
  }

  // Determines the node type.
  if (empty($settings['content_type'])) {
    $ct_types = node_get_types();
    $ct_options = array();
    if (is_array($ct_types)) {
      foreach ($ct_types as $key => $data) {
        $ct_options[$key] = $data->name;
      }
    }
    if (array_key_exists('story', $ct_options)) {
      $node->type = 'story';
    }
    else {
      $node->type = current(array_keys($ct_options));
    }
  }
  else {
    $node->type = $settings['content_type'];
  }
  if (feedapi_enabled_type($node->type)) {
    if ($error_msg !== TRUE) {
      drupal_set_message(t('Please disable FeedAPI for !item content-type.', array(
        '!item' => $node->type,
      )), 'error');
      $error_msg = TRUE;
    }
    return FALSE;
  }

  // Get the default options from the cont
  $options = variable_get('node_options_' . $node->type, FALSE);
  if (is_array($options)) {
    $node->status = in_array('status', $options) ? 1 : 0;
    $node->promote = in_array('promote', $options) ? 1 : 0;
    $node->sticky = in_array('sticky', $options) ? 1 : 0;
  }
  else {
    $node->status = 1;
  }
  $feed_node = node_load($feed_nid);
  $type = node_get_types('type', $node->type);

  // Use feedapi_mapper_map() if FeedAPI Mapper is version 2.x (= feedapi_mapper_unique() is present)
  if (function_exists('feedapi_mapper_map') && function_exists('feedapi_mapper_unique')) {
    $node = feedapi_mapper_map($feed_node, 'feedapi_node', $feed_item, $node);
    if (empty($node->teaser) && $type->has_body) {
      $node->teaser = node_teaser($node->body);
    }
  }
  else {
    if ($type->has_title) {
      $node->title = $feed_item->title;
    }
    if ($type->has_body) {
      $node->body = $feed_item->description;
      $node->teaser = node_teaser($feed_item->description);
    }
  }
  $node->format = isset($settings['input_format']) ? $settings['input_format'] : FILTER_FORMAT_DEFAULT;

  // Stick feed item on node so that add on modules can act on it.
  // A feed item can come in from more than one feed.
  $node->feedapi_node->feed_nids[$feed_nid] = $feed_nid;
  $node->feedapi_node->feed_item = $feed_item;

  // For backwards compatibility - todo: move to using feedapi_node->feed_nids and feedapi_node->feed_item.
  $node->feedapi->feed_nid = $feed_nid;
  $node->feedapi->feed_item = $feed_item;
  $node->created = time();
  node_object_prepare($node);
  if (!isset($feed_item->nid)) {
    $node->created = isset($settings['node_date']) && $settings['node_date'] == 'feed' ? $feed_item->options->timestamp : time();
  }
  else {
    $node->created = db_result(db_query("SELECT created FROM {node} WHERE nid = %d", $feed_item->nid));
  }
  $node->uid = $feed_node->uid;
  node_save($node);
  return $feed_item;
}

/**
 * Update a node which already assigned to a feed item
 */
function _feedapi_node_update($feed_item, $feed_nid, $settings, $id) {
  $feed_item->nid = $id;
  _feedapi_node_save($feed_item, $feed_nid, $settings);
  return $feed_item;
}

/**
 * Delete a node which already assigned to a feed item
 */
function _feedapi_node_delete($feed_item) {
  if (isset($feed_item->nid)) {
    _feedapi_node_node_delete($feed_item->nid);
  }
  else {

    // Let's throw an error on the off chance we land here.
    watchdog('feedapi_node', 'No nid on feed item to delete.');
  }
}

/**
 * Delete all nodes associated with a feed.
 */
function _feedapi_node_purge($feed) {
  $total = db_result(db_query('SELECT COUNT(*) FROM {feedapi_node_item_feed} WHERE feed_nid = %d', $feed->nid));
  $deleted = 0;
  $max_execution_time = ini_get('max_execution_time');
  while ($deleted < $total) {

    // Take hundred items at a time.
    $result = db_query_range('SELECT feed_item_nid as nid FROM {feedapi_node_item_feed} WHERE feed_nid = %d', $feed->nid, 0, 100);
    while ($node = db_fetch_object($result)) {
      node_delete($node->nid);
      $deleted++;

      // Stop 5 seconds before script time out.
      if ($max_execution_time - 5 < round(timer_read('page') / 1000)) {

        // Don't show all the node delete messages.
        drupal_get_messages();
        drupal_set_message(t('!deleted feed items of !total could be deleted before script time out - click <em>Remove items</em> again to delete more.', array(
          '!deleted' => $deleted,
          '!total' => $total,
        )));
        return;
      }
    }
  }
  if ($deleted) {

    // Don't show all the node delete messages.
    drupal_get_messages();
    drupal_set_message(t('!count feed items have been deleted.', array(
      '!count' => $deleted,
    )));
  }
  else {
    drupal_set_message(t('There were no feed items to delete'));
  }
}

/**
 * Determine whether a given feed item already exists or not.
 *
 * @param $feed_item
 *  Feed item object
 * @param $feed_nid
 *  Feed ID
 * @return
 *  TRUE if the item is new, FALSE if not.
 */
function _feedapi_node_unique($feed_item, $feed_nid, $settings) {

  // If FeedAPI Mapper 2.x is available, delegate decision.
  if (function_exists('feedapi_mapper_unique')) {
    $feed_node = node_load($feed_nid);
    $result = feedapi_mapper_unique($feed_node, 'feedapi_node', $feed_item);
    if ($result !== FALSE) {
      if (empty($result)) {
        return TRUE;
      }
      if (!$settings['x_dedupe']) {

        // Filter out unneeded result, only the items from the same feeds are needed
        foreach ($result as $field => $dup_items) {
          foreach ($dup_items as $item_id => $feed_nids) {
            if (!in_array($feed_nid, $feed_nids)) {
              unset($result[$field][$item_id]);
            }
          }
          if (count($result[$field]) == 0) {
            unset($result[$field]);
          }
        }
        if (empty($result)) {
          return TRUE;
        }

        // This is an item id
        return array_pop(array_keys(array_pop($result)));
      }
      else {
        foreach ($result as $dup_items) {
          foreach ($dup_items as $item_id => $feed_nids) {
            $feed_item->feedapi_node->duplicates[$item_id] = array_merge($feed_nids, (array) $feed_item->feedapi_node->duplicates[$item_id]);
          }
        }
        return $item_id;
      }
    }
  }

  // If we reach this point feedapi_mapper_unique() is not available or no unique elements have been defined.
  // Falls back to URL/GUID for determining whether item is unique.
  if (empty($feed_item->options->original_url)) {
    unset($feed_item->options->original_url);
  }
  if (empty($feed_item->options->guid)) {
    unset($feed_item->options->guid);
  }

  // Feed item is duplicate, if URL or GUID are duplicate or if they are both missing.
  if (isset($feed_item->options->original_url)) {
    $nid = db_result(db_query("SELECT fni.nid FROM {feedapi_node_item} fni JOIN {feedapi_node_item_feed} ff ON ff.feed_item_nid = fni.nid WHERE fni.url = '%s' AND ff.feed_nid = %d", $feed_item->options->original_url, $feed_nid));
    if ($nid !== FALSE) {
      return $nid;
    }
  }
  if (isset($feed_item->options->guid)) {
    $nid = db_result(db_query("SELECT fni.nid FROM {feedapi_node_item} fni JOIN {feedapi_node_item_feed} ff ON ff.feed_item_nid = fni.nid WHERE fni.guid = '%s' AND ff.feed_nid = %d", $feed_item->options->guid, $feed_nid));
    if ($nid !== FALSE) {
      return $nid;
    }
  }

  // If cross feed de-dupeing is enabled, check now whether there is a duplicate item on other feeds.
  // If so, store duplicates in array.
  // There is *usually* only one. However, there might be more than one.
  // Todo: don't link to feed items whose feed is not x_dedupe enabled.
  if ($settings['x_dedupe']) {
    if (isset($feed_item->options->original_url)) {
      $result = db_query("SELECT fni.nid, ff.feed_nid FROM {feedapi_node_item} fni JOIN {feedapi_node_item_feed} ff ON ff.feed_item_nid = fni.nid WHERE ff.feed_nid <> %d AND fni.url = '%s'", $feed_nid, $feed_item->options->original_url);
      while ($existing_feed_item = db_fetch_object($result)) {
        $feed_item->feedapi_node->duplicates[$existing_feed_item->nid][] = $existing_feed_item->feed_nid;
      }
    }
    if (!isset($feed_item->feedapi_node->duplicates) && isset($feed_item->options->guid)) {
      $result = db_query("SELECT fni.nid, ff.feed_nid FROM {feedapi_node_item} fni JOIN {feedapi_node_item_feed} ff ON ff.feed_item_nid = fni.nid WHERE ff.feed_nid <> %d AND fni.guid = '%s'", $feed_nid, $feed_item->options->guid);
      while ($existing_feed_item = db_fetch_object($result)) {
        $feed_item->feedapi_node->duplicates[$existing_feed_item->nid][] = $existing_feed_item->feed_nid;
      }
    }
  }
  if (isset($feed_item->options->original_url) || isset($feed_item->options->guid)) {
    return TRUE;
  }

  // Neither GUID, nor URL present: no unique item.
  return FALSE;
}

/**
 * Copy of http://api.drupal.org/api/function/node_delete/6 to avoid permission checking
 *
 * @todo: this is just a workaround to be able to delete nodes at cron time
 * @param unknown_type $nid
 */
function _feedapi_node_node_delete($nid) {
  $node = node_load($nid);
  db_query('DELETE FROM {node} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {node_revisions} WHERE nid = %d', $node->nid);

  // Call the node-specific callback (if any):
  node_invoke($node, 'delete');
  node_invoke_nodeapi($node, 'delete');

  // Clear the page and block caches.
  cache_clear_all();

  // Remove this node from the search index if needed.
  if (function_exists('search_wipe')) {
    search_wipe($node->nid, 'node');
  }
  watchdog('content', '@type: deleted %title.', array(
    '@type' => $node->type,
    '%title' => $node->title,
  ));
  drupal_set_message(t('@type %title has been deleted.', array(
    '@type' => node_get_types('name', $node),
    '%title' => $node->title,
  )));
}

Functions

Namesort descending Description
feedapi_node_ctools_plugin_api Implementation of hook_ctools_plugin_api().
feedapi_node_feedapi_item Implementation of hook_feedapi_item().
feedapi_node_feedapi_mapper_default Implementation of hook_feedapi_mapper_default(). Provides default mapping for FeedAPI Mapper 2.x
feedapi_node_feedapi_settings_form Implementation of hook_feedapi_settings_form(). If a module provides parsers and processors it MUST evaluate the $type variable to return different forms for parsers and processors. There might be a better term for parsers and processors than $type.
feedapi_node_help Implementation of hook_help().
feedapi_node_link Implementation of hook_link().
feedapi_node_nodeapi Implementation of hook_nodeapi().
feedapi_node_views_api Implementation of hook_node_views().
_feedapi_node_delete Delete a node which already assigned to a feed item
_feedapi_node_expire Check for expired items, pass them to the item_expire function
_feedapi_node_node_delete Copy of http://api.drupal.org/api/function/node_delete/6 to avoid permission checking
_feedapi_node_purge Delete all nodes associated with a feed.
_feedapi_node_save Create a node from the feed item Store the relationship between the node and the feed item
_feedapi_node_unique Determine whether a given feed item already exists or not.
_feedapi_node_update Update a node which already assigned to a feed item