| 1 |
<?php
|
| 2 |
// $Id: simplefeed_item.module,v 1.51 2007/11/16 17:48:37 m3avrck Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* A node feed item.
|
| 7 |
*/
|
| 8 |
|
| 9 |
|
| 10 |
/**
|
| 11 |
* Implementation of hook_init().
|
| 12 |
*/
|
| 13 |
function simplefeed_item_init() {
|
| 14 |
// ensure we are not serving a cached page
|
| 15 |
if (function_exists('drupal_set_content')) {
|
| 16 |
// we don't do this in hook_menu to ensure the files are already included when
|
| 17 |
// views_menu is executed
|
| 18 |
if (module_exists('views')) {
|
| 19 |
include_once('./'. drupal_get_path('module', 'simplefeed') .'/simplefeed_item_views.inc');
|
| 20 |
}
|
| 21 |
}
|
| 22 |
}
|
| 23 |
|
| 24 |
|
| 25 |
/**
|
| 26 |
* Implementation of hook_perm().
|
| 27 |
*/
|
| 28 |
function simplefeed_item_perm() {
|
| 29 |
return array('create feed items', 'edit own feed items', 'edit feed items');
|
| 30 |
}
|
| 31 |
|
| 32 |
|
| 33 |
/**
|
| 34 |
* Implementation of hook_access().
|
| 35 |
*/
|
| 36 |
function simplefeed_item_access($op, $node) {
|
| 37 |
global $user;
|
| 38 |
|
| 39 |
switch ($op) {
|
| 40 |
case 'create':
|
| 41 |
return user_access('create feed items');
|
| 42 |
break;
|
| 43 |
|
| 44 |
case 'update':
|
| 45 |
case 'delete':
|
| 46 |
// users who create a feed may edit or delete it later, assuming they have the necessary permissions
|
| 47 |
if ((user_access('edit own feed items') && ($user->uid == $node->uid)) || user_access('edit feed items')) {
|
| 48 |
return TRUE;
|
| 49 |
}
|
| 50 |
break;
|
| 51 |
}
|
| 52 |
}
|
| 53 |
|
| 54 |
|
| 55 |
/**
|
| 56 |
* Implementation of hook_node_info().
|
| 57 |
*/
|
| 58 |
function simplefeed_item_node_info() {
|
| 59 |
return array(
|
| 60 |
'feed_item' => array(
|
| 61 |
'name' => t('Feed Item'),
|
| 62 |
'module' => 'simplefeed_item',
|
| 63 |
'description' => t('An item that is part of a parent feed.')
|
| 64 |
)
|
| 65 |
);
|
| 66 |
}
|
| 67 |
|
| 68 |
|
| 69 |
/**
|
| 70 |
* Implementation of hook_form().
|
| 71 |
*/
|
| 72 |
function simplefeed_item_form(&$node) {
|
| 73 |
$type = node_get_types('type', $node);
|
| 74 |
|
| 75 |
$form['title'] = array(
|
| 76 |
'#type' => 'textfield',
|
| 77 |
'#title' => check_plain($type->title_label),
|
| 78 |
'#required' => TRUE,
|
| 79 |
'#default_value' => $node->title,
|
| 80 |
'#weight' => -5,
|
| 81 |
);
|
| 82 |
$form['body_filter']['body'] = array(
|
| 83 |
'#type' => 'textarea',
|
| 84 |
'#title' => check_plain($type->body_label),
|
| 85 |
'#default_value' => $node->body,
|
| 86 |
'#rows' => 3,
|
| 87 |
);
|
| 88 |
$form['body_filter']['format'] = filter_form($node->format);
|
| 89 |
|
| 90 |
$form['url'] = array(
|
| 91 |
'#type' => 'textfield',
|
| 92 |
'#title' => t('URL'),
|
| 93 |
'#description' => t('The URL for this feed item.'),
|
| 94 |
'#maxlength' => 255,
|
| 95 |
'#default_value' => isset($node->url) ? $node->url : 'http://www.',
|
| 96 |
'#required' => TRUE
|
| 97 |
);
|
| 98 |
|
| 99 |
$form['feed_item_settings'] = array(
|
| 100 |
'#type' => 'fieldset',
|
| 101 |
'#title' => t('Feed item settings'),
|
| 102 |
'#collapsible' => TRUE,
|
| 103 |
'#collapsed' => TRUE,
|
| 104 |
);
|
| 105 |
|
| 106 |
// note all feed items are linked to their parent feed through the feed's nid
|
| 107 |
// this allows the main feed to be revisioned (e.g., change title, description, etc.)
|
| 108 |
// but still keep the same list of feed items
|
| 109 |
// we select from the node table to get the latest revision's data
|
| 110 |
$feeds = array();
|
| 111 |
$result = db_query("SELECT title, nid FROM {node} WHERE type = 'feed'");
|
| 112 |
while ($feed = db_fetch_object($result)) {
|
| 113 |
$feeds[$feed->nid] = $feed->title;
|
| 114 |
}
|
| 115 |
|
| 116 |
// can't add a feed item if no feeds exist
|
| 117 |
if (count($feeds) < 1) {
|
| 118 |
drupal_set_message(t('No feeds found. You must first <a href="!create_feed">create a feed</a> before adding feed items.', array('!create_feed' => url('node/add/feed'))), 'error');
|
| 119 |
}
|
| 120 |
|
| 121 |
$form['feed_item_settings']['iid'] = array('#type' => 'value', '#value' => $node->iid);
|
| 122 |
|
| 123 |
$form['feed_item_settings']['fid'] = array(
|
| 124 |
'#type' => 'select',
|
| 125 |
'#title' => t('Select parent feed'),
|
| 126 |
'#description' => t('Select the parent feed for this feed item.'),
|
| 127 |
'#options' => $feeds,
|
| 128 |
'#default_value' => $node->fid,
|
| 129 |
);
|
| 130 |
|
| 131 |
if (user_access('administer feeds')) {
|
| 132 |
$period = array(0 => t('Never')) + drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
|
| 133 |
$form['feed_item_settings']['expires'] = array(
|
| 134 |
'#type' => 'select',
|
| 135 |
'#title' => t('Discard item older than'),
|
| 136 |
'#default_value' => isset($node->expires) ? $node->expires : variable_get('simplefeed_expires', 86400),
|
| 137 |
'#options' => $period,
|
| 138 |
'#description' => t('This feed item will be automatically discarded. Requires !cron to be running.', array('!cron' => l('cron', 'admin/logs/status')))
|
| 139 |
);
|
| 140 |
}
|
| 141 |
else {
|
| 142 |
$form['feed_item_settings']['expires'] = array('#type' => 'value', '#value' => variable_get('simplefeed_expires', 86400));
|
| 143 |
}
|
| 144 |
|
| 145 |
return $form;
|
| 146 |
}
|
| 147 |
|
| 148 |
|
| 149 |
/**
|
| 150 |
* Implementation of hook_validate().
|
| 151 |
*
|
| 152 |
* @todo - currently there is no check for duplicate feed items if entered manually
|
| 153 |
* we would need to have SimplePie library generate a hash for this feed item
|
| 154 |
*/
|
| 155 |
function simplefeed_item_validate($node) {
|
| 156 |
$valid_url = "`(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?`";
|
| 157 |
$url = trim($node->url);
|
| 158 |
|
| 159 |
if (!preg_match($valid_url, $url)) {
|
| 160 |
form_set_error('url', t('The URL entered is not valid. It should be in the format of: <em>http://www.example.com/link/to/feed/item.html</em>'));
|
| 161 |
}
|
| 162 |
|
| 163 |
if (!$node->fid) {
|
| 164 |
form_set_error('fid', t('You must select a valid parent feed.'));
|
| 165 |
}
|
| 166 |
}
|
| 167 |
|
| 168 |
|
| 169 |
/**
|
| 170 |
* Implementation of hook_load().
|
| 171 |
*/
|
| 172 |
function simplefeed_item_load($node) {
|
| 173 |
$additions = db_fetch_object(db_query('SELECT iid, fid, expires, url FROM {simplefeed_feed_item} WHERE vid = %d', $node->vid));
|
| 174 |
return $additions;
|
| 175 |
}
|
| 176 |
|
| 177 |
|
| 178 |
/**
|
| 179 |
* Implementation of hook_insert().
|
| 180 |
*
|
| 181 |
* @todo - currently feed items that are manually created have blank iid -- need a way to invoke SimplePie to generate an iid for duplicate checking
|
| 182 |
*/
|
| 183 |
function simplefeed_item_insert($node) {
|
| 184 |
db_query("INSERT INTO {simplefeed_feed_item} (vid, nid, iid, fid, expires, url) VALUES (%d, %d, '%s', %d, %d, '%s')", $node->vid, $node->nid, $node->iid, $node->fid, $node->expires, $node->url);
|
| 185 |
}
|
| 186 |
|
| 187 |
|
| 188 |
/**
|
| 189 |
* Implementation of hook_update().
|
| 190 |
*/
|
| 191 |
function simplefeed_item_update($node) {
|
| 192 |
// if this is a new node or we're adding a new revision
|
| 193 |
if ($node->revision) {
|
| 194 |
simplefeed_item_insert($node);
|
| 195 |
}
|
| 196 |
else {
|
| 197 |
db_query("UPDATE {simplefeed_feed_item} SET fid = %d, expires = %d, url = '%s' WHERE vid = %d", $node->fid, $node->expires, $node->url, $node->vid);
|
| 198 |
}
|
| 199 |
}
|
| 200 |
|
| 201 |
|
| 202 |
/**
|
| 203 |
* Implementation of hook_delete().
|
| 204 |
*/
|
| 205 |
function simplefeed_item_delete($node) {
|
| 206 |
db_query('DELETE FROM {simplefeed_feed_item} WHERE nid = %d', $node->nid);
|
| 207 |
}
|
| 208 |
|
| 209 |
|
| 210 |
/**
|
| 211 |
* Implementation of hook_nodeapi().
|
| 212 |
*/
|
| 213 |
function simplefeed_item_nodeapi(&$node, $op, $teaser, $page) {
|
| 214 |
if ($node->type == 'feed_item') {
|
| 215 |
switch ($op) {
|
| 216 |
case 'delete revision':
|
| 217 |
db_query('DELETE FROM {simplefeed_feed_item} WHERE vid = %d', $node->vid);
|
| 218 |
break;
|
| 219 |
case 'submit':
|
| 220 |
// Have to set the date and author here, because non-node administrators aren't able to
|
| 221 |
// change it in node_submit() so feeds added in anon cron jobs will show up as now.
|
| 222 |
$node->created = $node->date ? strtotime($node->date) : NULL;
|
| 223 |
$account = user_load(array('name' => $node->name));
|
| 224 |
$node->uid = $account->uid;
|
| 225 |
break;
|
| 226 |
}
|
| 227 |
}
|
| 228 |
}
|
| 229 |
|
| 230 |
|
| 231 |
/**
|
| 232 |
* Implementation of hook_view().
|
| 233 |
*/
|
| 234 |
function simplefeed_item_view($node, $teaser = FALSE, $page = FALSE) {
|
| 235 |
$node = node_prepare($node, $teaser);
|
| 236 |
|
| 237 |
$node->content['simplefeed_item']['#theme'] = 'simplefeed_item_node_view';
|
| 238 |
$node->content['simplefeed_item']['#weight'] = 2;
|
| 239 |
$node->content['simplefeed_item']['url'] = array(
|
| 240 |
'#value' => check_url($node->url),
|
| 241 |
'#weight' => 1,
|
| 242 |
);
|
| 243 |
|
| 244 |
// since only administrators can edit when a feed item expires, only admins can see what this value is
|
| 245 |
if (user_access('administer feeds')) {
|
| 246 |
$node->content['simplefeed_item']['expires'] = array(
|
| 247 |
'#value' => format_interval($node->expires),
|
| 248 |
'#weight' => 2,
|
| 249 |
);
|
| 250 |
}
|
| 251 |
|
| 252 |
return $node;
|
| 253 |
}
|
| 254 |
|
| 255 |
|
| 256 |
/**
|
| 257 |
* Theme the display of a feed node.
|
| 258 |
*/
|
| 259 |
function theme_simplefeed_item_node_view($values) {
|
| 260 |
// needs to be a space so default values aren't outputed
|
| 261 |
$output = ' ';
|
| 262 |
|
| 263 |
// since only administrators can edit when a feed item expires, only admins can see what this value is
|
| 264 |
if (user_access('administer feeds')) {
|
| 265 |
drupal_add_css(drupal_get_path('module', 'simplefeed') .'/simplefeed.css');
|
| 266 |
|
| 267 |
$output = '<ul class="simplefeed-item">';
|
| 268 |
$output .= '<li><strong>Expires:</strong> '. $values['expires']['#value'] .'</li>';
|
| 269 |
$output .= '</ul>';
|
| 270 |
}
|
| 271 |
|
| 272 |
return $output;
|
| 273 |
}
|
| 274 |
|
| 275 |
|
| 276 |
/**
|
| 277 |
* Implementation of hook_link().
|
| 278 |
*/
|
| 279 |
function simplefeed_item_link($type, $node = NULL, $teaser = FALSE) {
|
| 280 |
$links = array();
|
| 281 |
static $feeds = array();
|
| 282 |
|
| 283 |
if ($type == 'node' && $node->type == 'feed_item' && isset($node->fid)) {
|
| 284 |
// since generally you'll see the same feed items in a row for the same feed
|
| 285 |
// we static cache this query so it doesn't repeat 10x or more per page
|
| 286 |
if (!isset($feeds[$node->fid])) {
|
| 287 |
$feed = db_result(db_query('SELECT title FROM {node} WHERE nid = %d', $node->fid));
|
| 288 |
$feeds[$node->fid] = $feed;
|
| 289 |
}
|
| 290 |
else {
|
| 291 |
$feed = $feeds[$node->fid];
|
| 292 |
}
|
| 293 |
$links['simplefeed_item_parent'] = array(
|
| 294 |
'title' => $feed,
|
| 295 |
'href' => 'node/'. $node->fid,
|
| 296 |
'attributes' => array('title' => t('Goto the feed source for this content'))
|
| 297 |
);
|
| 298 |
$links['simplefeed_item_url'] = array(
|
| 299 |
'title' => t('Source'),
|
| 300 |
// note we don't need to check_url() since we do this in hook_view() on line 244
|
| 301 |
// we decode_entities() to prevent double encoding since this url was already checked above
|
| 302 |
// that way if a user outputs this at the theme layer the url is still safe
|
| 303 |
'href' => decode_entities($node->url),
|
| 304 |
'absolute' => TRUE,
|
| 305 |
'attributes' => array('title' => t('Goto the source of this post'))
|
| 306 |
);
|
| 307 |
}
|
| 308 |
|
| 309 |
return $links;
|
| 310 |
}
|
| 311 |
|
| 312 |
|
| 313 |
/**
|
| 314 |
* Delete any feed items that have expired.
|
| 315 |
*/
|
| 316 |
function simplefeed_item_feed_expire() {
|
| 317 |
// delete all expired feed items, ignore $vid since we expire *every* feed item in one call, not specific ones
|
| 318 |
// note if a feed item has multiple revisions, any revision's expiration can trigger deletion of the entire node
|
| 319 |
$items = db_query('SELECT DISTINCT n.nid FROM {simplefeed_feed_item} s JOIN {node_revisions} n ON s.vid = n.vid WHERE s.expires <> 0 AND s.expires <= (%d - n.timestamp)', time());
|
| 320 |
|
| 321 |
while ($item = db_fetch_object($items)) {
|
| 322 |
node_delete($item->nid);
|
| 323 |
}
|
| 324 |
|
| 325 |
// if we actually deleted something
|
| 326 |
if ($items != '') {
|
| 327 |
// since we can end up deleting a lot of feed items quite often, we optimize this table to save space
|
| 328 |
global $db_type;
|
| 329 |
if ($db_type == 'mysql' || $db_type == 'mysqli') {
|
| 330 |
db_query('OPTIMIZE TABLE {simplefeed_feed_item}');
|
| 331 |
}
|
| 332 |
}
|
| 333 |
}
|
| 334 |
|
| 335 |
|
| 336 |
/**
|
| 337 |
* Turn each feed item into a node.
|
| 338 |
*
|
| 339 |
* @param $process_feed
|
| 340 |
* Feed node object
|
| 341 |
* @param $cache_path
|
| 342 |
* Where to cache this feed on the file system
|
| 343 |
*/
|
| 344 |
function simplefeed_item_feed_parse($process_feed, $cache_path) {
|
| 345 |
// simplepie processing, fetch feeds
|
| 346 |
include_once './'. drupal_get_path('module', 'simplefeed') .'/simplepie.inc';
|
| 347 |
$feed = new SimplePie();
|
| 348 |
|
| 349 |
/*
|
| 350 |
You tell SimplePie what feed you want to get and where to cache it at.
|
| 351 |
SimplePie looks to see if the feed is already cached:
|
| 352 |
If the cache is fresh use that.
|
| 353 |
If there is no cached copy at all, SimplePie will grab and cache the feed.
|
| 354 |
If the cache is there but it's old (SimplePie defaults to 60 minutes; configurable with set_cache_duration()), then SimplePie will ask the feed if it has changed since the last time we grabbed it (this is the HTTPCG part).
|
| 355 |
If it hasn't changed, we reset the timer on the cache's freshness and keep it for another 60 minutes before checking again.
|
| 356 |
If the cache has changed, SimplePie dumps the existing cache (since the cache is just a copy of the feed), and grabs a new copy of the feed and uses it.
|
| 357 |
|
| 358 |
This cache uses a combination of LAST-MODIFIED and ETAG headers, and other intelligent methods to only grab changed feeds.
|
| 359 |
*/
|
| 360 |
|
| 361 |
$feed->set_cache_location($_SERVER['DOCUMENT_ROOT'] . base_path() . $cache_path);
|
| 362 |
$feed->set_feed_url($process_feed->url);
|
| 363 |
$feed->set_timeout(15);
|
| 364 |
// prevent SimplePie from using all of it's data santization since we use Drupal's input formats to handle this
|
| 365 |
$feed->set_stupidly_fast(TRUE);
|
| 366 |
$success = $feed->init();
|
| 367 |
|
| 368 |
if ($success && $feed->data) {
|
| 369 |
// loop through all of the items in the feed, faster than foreach
|
| 370 |
$max = $feed->get_item_quantity();
|
| 371 |
|
| 372 |
for ($i = 0; $i < $max; $i++) {
|
| 373 |
$item = $feed->get_item($i);
|
| 374 |
// unique SHA-1 hash for a feed item
|
| 375 |
$iid = $item->get_id(true);
|
| 376 |
|
| 377 |
// make sure we don't already have this feed item
|
| 378 |
$duplicate = db_result(db_query("SELECT iid FROM {simplefeed_feed_item} WHERE iid = '%s'", $iid));
|
| 379 |
|
| 380 |
if ($duplicate == '') {
|
| 381 |
// add in tags if a vocabulary is set
|
| 382 |
if (variable_get('simplefeed_vocab', 0)) {
|
| 383 |
// add in tags from parent feed
|
| 384 |
$tags = $process_feed->tags;
|
| 385 |
// add in any tags that are found in the feed item itself from the originating site
|
| 386 |
if (variable_get('simplefeed_categories', 0)) {
|
| 387 |
if (count($item->get_categories()) > 0) {
|
| 388 |
foreach ($item->get_categories() as $category) {
|
| 389 |
$tags .= ', '. $category->get_label();
|
| 390 |
}
|
| 391 |
}
|
| 392 |
}
|
| 393 |
|
| 394 |
// add any tags (either inheriting from parent feed or from parsing the feed item) to the feed item node
|
| 395 |
if ($tags != '') {
|
| 396 |
$item->taxonomy['tags'][$process_feed->vocab] = decode_entities($tags);
|
| 397 |
}
|
| 398 |
}
|
| 399 |
|
| 400 |
$link = $item->get_permalink();
|
| 401 |
// this is node created date format for Drupal
|
| 402 |
$date = $item->get_date('Y-m-d H:i:s O');
|
| 403 |
$body = $item->get_content();
|
| 404 |
// this strips out any tags that may appear as <b> in the title, and makes sure " -> " for display
|
| 405 |
$title = strip_tags(decode_entities($item->get_title()));
|
| 406 |
|
| 407 |
// some feeds don't provide titles so we construct one with the first 72 characters of the body
|
| 408 |
if (!$title) {
|
| 409 |
// remove any HTML or line breaks so these don't appear in the title
|
| 410 |
$title = trim(str_replace(array("\n", "\r"), ' ', strip_tags($body)));
|
| 411 |
$title = trim(substr($title, 0, 72));
|
| 412 |
$lastchar = substr($title, -1, 1);
|
| 413 |
// check to see if the last character in the title is a non-alphanumeric character, except for ? or !
|
| 414 |
// if it is strip it off so you don't get strange looking titles
|
| 415 |
if (preg_match('/[^0-9A-Za-z\!\?]/', $lastchar)) {
|
| 416 |
$title = substr($title, 0, -1);
|
| 417 |
}
|
| 418 |
// ? and ! are ok to end a title with since they make sense
|
| 419 |
if ($lastchar != '!' and $lastchar != '?') {
|
| 420 |
$title .= '...';
|
| 421 |
}
|
| 422 |
}
|
| 423 |
|
| 424 |
// create a feed item node
|
| 425 |
$node = array('type' => 'feed_item', 'iid' => $iid);
|
| 426 |
$values['title'] = $title;
|
| 427 |
if ($date) {
|
| 428 |
// "created" is a node property, however we have to use "date" to set this with drupal_execute since it is the form element name
|
| 429 |
$values['date'] = $date;
|
| 430 |
}
|
| 431 |
$values['name'] = db_result(db_query('SELECT u.name FROM {node} n INNER JOIN {users} u ON n.uid = u.uid WHERE nid = %d', $process_feed->nid));
|
| 432 |
$values['format'] = variable_get('simplefeed_format', 1);
|
| 433 |
// $item->get_description() for teaser?
|
| 434 |
$values['body'] = $body;
|
| 435 |
$values['expires'] = $process_feed->expires;
|
| 436 |
$values['url'] = $link != '' ? $link : $feed->get_permalink();
|
| 437 |
$values['fid'] = $process_feed->nid;
|
| 438 |
$values['taxonomy'] = $item->taxonomy;
|
| 439 |
|
| 440 |
// create a new feed-item node, adding in all of the other node defaults
|
| 441 |
drupal_execute('feed_item_node_form', $values, $node);
|
| 442 |
}
|
| 443 |
|
| 444 |
// we unset $item each time to prevent any pass by reference memory leaks that PHP encounters with objects in foreach loops
|
| 445 |
unset($item);
|
| 446 |
}
|
| 447 |
}
|
| 448 |
else if (isset($feed->error)) {
|
| 449 |
watchdog('simplefeed', t('The feed %feed could not be processed due to the following error: %error', array('%feed' => $process_feed->title, '%error' => $feed->error)), WATCHDOG_ERROR, l('view', 'node/'. $process_feed->nid));
|
| 450 |
}
|
| 451 |
}
|