nid); $f = db_fetch_object($result); if (is_object($f)) { $node->feedapi_node = $f; $feed_nids[$f->feed_nid] = $f->feed_nid; $node->feedapi_node->feed_nids = $feed_nids; unset($node->feedapi_node->feed_nid); } break; case 'insert': if ($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 ($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); } break; } } /** * Implementation of hook_link(). */ function feedapi_node_link($type, $node = NULL) { if ($type == 'node') { if ($node->feedapi_node) { $result = db_query("SELECT n.title, n.nid FROM {node} n WHERE n.nid IN (%s) ORDER BY title DESC", implode(', ', $node->feedapi_node->feed_nids)); $owner_feeds_num = db_num_rows($result); 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_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(); if (is_array($ct_types)) { foreach ($ct_types as $key => $data) { if (!feedapi_enabled_type($key)) { $ct_options[$key] = $data->name; } } } if (array_key_exists('story', $ct_options)) { $default_type = 'story'; } else { $default_type = current(array_keys($ct_options)); } $form['content_type'] = array( '#type' => 'select', '#title' => t('Node type of feed items'), '#default_value' => $default_type, '#options' => $ct_options, '#description' => t('Choose the node type 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['promote'] = array( '#type' => 'textfield', '#title' => t('Promoted items'), '#description' => t('The newest N items per feed will be promoted to front page. Leave empty and FeedAPI does not alter the promote property of the feed items.'), '#default_value' => 3, ); $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"); default: if (function_exists('_feedapi_node_'. $op)) { $args = array_slice(func_get_args(), 1); return call_user_func_array('_feedapi_node_'. $op, $args); } } } /** * Implements hook_feedapi_after_refresh($feed). * Handle the promote N items to the frontpage setting */ function feedapi_node_feedapi_after_refresh($feed) { if ($promote = $feed->settings['processors']['feedapi_node']['promote']) { $result = db_query("SELECT n.nid FROM {node} n JOIN {feedapi_node_item_feed} ff ON ff.feed_item_nid = n.nid WHERE ff.feed_nid = %d AND n.promote = 1", $feed->nid); $nids_promoted_now = array(); $nids_top_n = array(); while ($item = db_fetch_array($result)) { $nids_promoted_now[] = $item['nid']; } $result = pager_query("SELECT n.nid FROM {node} n JOIN {feedapi_node_item_feed} ff ON ff.feed_item_nid = n.nid WHERE ff.feed_nid = %d ORDER BY n.created DESC", $promote, 0, NULL, $feed->nid); while ($item = db_fetch_array($result)) { $nids_top_n[] = $item['nid']; } $to_promote = array_diff($nids_top_n, $nids_promoted_now); $to_demote = array_diff($nids_promoted_now, $nids_top_n); if (count($to_promote) > 0) { db_query("UPDATE {node} SET promote = 1 WHERE nid IN (%s)", implode(',', $to_promote)); } if (count($to_demote) > 0) { db_query("UPDATE {node} SET promote = 0 WHERE nid IN (%s)", implode(',', $to_demote)); } } } /** * 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; // Don't save anything if neither url nor guid given. if (!$feed_item->options->original_url) { if (!$feed_item->options->guid) { return $feed_item; } } // Construct the node object $node = new stdClass(); if (isset($feed_item->nid)) { $node->nid = $feed_item->nid; } $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); $node->title = $feed_item->title; if (empty($node->title) && !empty($feed_item->description)) { // Explode to words and use the first 3 words. $words = preg_split("/[\s,]+/", $feed_item->description); $node->title = $words[0] .' '. $words[1] .' '. $words[2]; } $node->uid = $feed_node->uid; 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->body = $feed_item->description; $node->teaser = node_teaser($feed_item->description); // 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_object_prepare($node); // 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); } } else { 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 = array()) { // Determine which node is assigned to this item if ($feed_item->options->guid) { $node = db_fetch_object(db_query("SELECT nid FROM {feedapi_node_item} WHERE guid = '%s'", $feed_item->options->guid)); } else { $node = db_fetch_object(db_query("SELECT nid FROM {feedapi_node_item} WHERE url = '%s'", $feed_item->options->original_url)); } $feed_item->nid = $node->nid; _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', t('No nid on feed item to delete.')); } } /** * Add to the feed item object some data to identify the node assigned */ function _feedapi_node_load($feed_item, $feed_nid) { $item = db_fetch_object(db_query("SELECT * FROM {feedapi_node_item} WHERE nid = %d", $feed_item->nid)); $feed_item->nid = $item->nid; $feed_item->arrived = $item->arrived; $feed_item->options->original_url = $item->url; $feed_item->options->guid = $item->guid; $feed_item->options->timestamp = $item->timestamp; $content = node_load($item->nid); $feed_item->description = $content->body; $feed_item->title = $content->title; $feed_item->teaser = $content->teaser; return $feed_item; } /** * Construct the basic information (nid, feed_nid) of all feeds into an array. * * @param $feed * Feed object * @return * The array of feed elements with basic information */ function _feedapi_node_fetch($feed) { $result = db_query("SELECT fni.nid, ff.feed_nid, fni.arrived, fni.timestamp FROM {feedapi_node_item} fni JOIN {feedapi_node_item_feed} ff ON ff.feed_item_nid = fni.nid WHERE ff.feed_nid = %d ORDER BY fni.timestamp DESC", $feed->nid); $items = array(); while ($item = db_fetch_object($result)) { $node = node_load($item->nid); $item->title = $node->title; $items[] = $item; } return $items; } /** * Tell if the feed item was seen before or not at the feed * * @param $feed_item * Feed item object * @param $feed_nid * Feed ID * @return * TRUE if the item is new, FALSE if the item is a duplicated one */ function _feedapi_node_unique($feed_item, $feed_nid, $settings) { // Feed item is duplicate, if URL or GUID are duplicate or if they are both missing. if (isset($feed_item->options->original_url)) { $count = 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 ($count) { return FALSE; } } if (isset($feed_item->options->guid)) { $count = 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 ($count) { return FALSE; } } // 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'); } drupal_set_message(t('%title has been deleted.', array('%title' => $node->title))); watchdog('content', t('@type: deleted %title.', array('@type' => t($node->type), '%title' => $node->title))); }