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 Remove items 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))); } // OR connection between the fields // If no cross-feed deduping, it's over here, the array is 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))); }