| [ Index ] |
PHP Cross Reference of Drupal 6 (gatewave) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: aggregator.module,v 1.374.2.6 2010/05/28 16:06:59 goba Exp $ 3 4 /** 5 * @file 6 * Used to aggregate syndicated content (RSS, RDF, and Atom). 7 */ 8 9 /** 10 * Implementation of hook_help(). 11 */ 12 function aggregator_help($path, $arg) { 13 switch ($path) { 14 case 'admin/help#aggregator': 15 $output = '<p>'. t('The aggregator is a powerful on-site syndicator and news reader that gathers fresh content from RSS-, RDF-, and Atom-based feeds made available across the web. Thousands of sites (particularly news sites and blogs) publish their latest headlines and posts in feeds, using a number of standardized XML-based formats. Formats supported by the aggregator include <a href="@rss">RSS</a>, <a href="@rdf">RDF</a>, and <a href="@atom">Atom</a>.', array('@rss' => 'http://cyber.law.harvard.edu/rss/', '@rdf' => 'http://www.w3.org/RDF/', '@atom' => 'http://www.atomenabled.org')) .'</p>'; 16 $output .= '<p>'. t('Feeds contain feed items, or individual posts published by the site providing the feed. Feeds may be grouped in categories, generally by topic. Users view feed items in the <a href="@aggregator">main aggregator display</a> or by <a href="@aggregator-sources">their source</a>. Administrators can <a href="@feededit">add, edit and delete feeds</a> and choose how often to check each feed for newly updated items. The most recent items in either a feed or category can be displayed as a block through the <a href="@admin-block">blocks administration page</a>. A <a href="@aggregator-opml">machine-readable OPML file</a> of all feeds is available. A correctly configured <a href="@cron">cron maintenance task</a> is required to update feeds automatically.', array('@aggregator' => url('aggregator'), '@aggregator-sources' => url('aggregator/sources'), '@feededit' => url('admin/content/aggregator'), '@admin-block' => url('admin/build/block'), '@aggregator-opml' => url('aggregator/opml'), '@cron' => url('admin/reports/status'))) .'</p>'; 17 $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@aggregator">Aggregator module</a>.', array('@aggregator' => 'http://drupal.org/handbook/modules/aggregator/')) .'</p>'; 18 return $output; 19 case 'admin/content/aggregator': 20 $output = '<p>'. t('Thousands of sites (particularly news sites and blogs) publish their latest headlines and posts in feeds, using a number of standardized XML-based formats. Formats supported by the aggregator include <a href="@rss">RSS</a>, <a href="@rdf">RDF</a>, and <a href="@atom">Atom</a>.', array('@rss' => 'http://cyber.law.harvard.edu/rss/', '@rdf' => 'http://www.w3.org/RDF/', '@atom' => 'http://www.atomenabled.org')) .'</p>'; 21 $output .= '<p>'. t('Current feeds are listed below, and <a href="@addfeed">new feeds may be added</a>. For each feed or feed category, the <em>latest items</em> block may be enabled at the <a href="@block">blocks administration page</a>.', array('@addfeed' => url('admin/content/aggregator/add/feed'), '@block' => url('admin/build/block'))) .'</p>'; 22 return $output; 23 case 'admin/content/aggregator/add/feed': 24 return '<p>'. t('Add a feed in RSS, RDF or Atom format. A feed may only have one entry.') .'</p>'; 25 case 'admin/content/aggregator/add/category': 26 return '<p>'. t('Categories allow feed items from different feeds to be grouped together. For example, several sport-related feeds may belong to a category named <em>Sports</em>. Feed items may be grouped automatically (by selecting a category when creating or editing a feed) or manually (via the <em>Categorize</em> page available from feed item listings). Each category provides its own feed page and block.') .'</p>'; 27 } 28 } 29 30 /** 31 * Implementation of hook_theme() 32 */ 33 function aggregator_theme() { 34 return array( 35 'aggregator_wrapper' => array( 36 'arguments' => array('content' => NULL), 37 'file' => 'aggregator.pages.inc', 38 'template' => 'aggregator-wrapper', 39 ), 40 'aggregator_categorize_items' => array( 41 'arguments' => array('form' => NULL), 42 'file' => 'aggregator.pages.inc', 43 ), 44 'aggregator_feed_source' => array( 45 'arguments' => array('feed' => NULL), 46 'file' => 'aggregator.pages.inc', 47 'template' => 'aggregator-feed-source', 48 ), 49 'aggregator_block_item' => array( 50 'arguments' => array('item' => NULL, 'feed' => 0), 51 ), 52 'aggregator_summary_items' => array( 53 'arguments' => array('summary_items' => NULL, 'source' => NULL), 54 'file' => 'aggregator.pages.inc', 55 'template' => 'aggregator-summary-items', 56 ), 57 'aggregator_summary_item' => array( 58 'arguments' => array('item' => NULL), 59 'file' => 'aggregator.pages.inc', 60 'template' => 'aggregator-summary-item', 61 ), 62 'aggregator_item' => array( 63 'arguments' => array('item' => NULL), 64 'file' => 'aggregator.pages.inc', 65 'template' => 'aggregator-item', 66 ), 67 'aggregator_page_opml' => array( 68 'arguments' => array('feeds' => NULL), 69 'file' => 'aggregator.pages.inc', 70 ), 71 'aggregator_page_rss' => array( 72 'arguments' => array('feeds' => NULL, 'category' => NULL), 73 'file' => 'aggregator.pages.inc', 74 ), 75 ); 76 } 77 78 /** 79 * Implementation of hook_menu(). 80 */ 81 function aggregator_menu() { 82 $items['admin/content/aggregator'] = array( 83 'title' => 'Feed aggregator', 84 'description' => "Configure which content your site aggregates from other sites, how often it polls them, and how they're categorized.", 85 'page callback' => 'aggregator_admin_overview', 86 'access arguments' => array('administer news feeds'), 87 'file' => 'aggregator.admin.inc', 88 ); 89 $items['admin/content/aggregator/add/feed'] = array( 90 'title' => 'Add feed', 91 'page callback' => 'drupal_get_form', 92 'page arguments' => array('aggregator_form_feed'), 93 'access arguments' => array('administer news feeds'), 94 'type' => MENU_LOCAL_TASK, 95 'parent' => 'admin/content/aggregator', 96 'file' => 'aggregator.admin.inc', 97 ); 98 $items['admin/content/aggregator/add/category'] = array( 99 'title' => 'Add category', 100 'page callback' => 'drupal_get_form', 101 'page arguments' => array('aggregator_form_category'), 102 'access arguments' => array('administer news feeds'), 103 'type' => MENU_LOCAL_TASK, 104 'parent' => 'admin/content/aggregator', 105 'file' => 'aggregator.admin.inc', 106 ); 107 $items['admin/content/aggregator/remove/%aggregator_feed'] = array( 108 'title' => 'Remove items', 109 'page callback' => 'drupal_get_form', 110 'page arguments' => array('aggregator_admin_remove_feed', 4), 111 'access arguments' => array('administer news feeds'), 112 'type' => MENU_CALLBACK, 113 'file' => 'aggregator.admin.inc', 114 ); 115 $items['admin/content/aggregator/update/%aggregator_feed'] = array( 116 'title' => 'Update items', 117 'page callback' => 'aggregator_admin_refresh_feed', 118 'page arguments' => array(4), 119 'access arguments' => array('administer news feeds'), 120 'type' => MENU_CALLBACK, 121 'file' => 'aggregator.admin.inc', 122 ); 123 $items['admin/content/aggregator/list'] = array( 124 'title' => 'List', 125 'type' => MENU_DEFAULT_LOCAL_TASK, 126 'weight' => -10, 127 ); 128 $items['admin/content/aggregator/settings'] = array( 129 'title' => 'Settings', 130 'page callback' => 'drupal_get_form', 131 'page arguments' => array('aggregator_admin_settings'), 132 'type' => MENU_LOCAL_TASK, 133 'weight' => 10, 134 'access arguments' => array('administer news feeds'), 135 'file' => 'aggregator.admin.inc', 136 ); 137 $items['aggregator'] = array( 138 'title' => 'Feed aggregator', 139 'page callback' => 'aggregator_page_last', 140 'access arguments' => array('access news feeds'), 141 'weight' => 5, 142 'file' => 'aggregator.pages.inc', 143 ); 144 $items['aggregator/sources'] = array( 145 'title' => 'Sources', 146 'page callback' => 'aggregator_page_sources', 147 'access arguments' => array('access news feeds'), 148 'file' => 'aggregator.pages.inc', 149 ); 150 $items['aggregator/categories'] = array( 151 'title' => 'Categories', 152 'page callback' => 'aggregator_page_categories', 153 'access callback' => '_aggregator_has_categories', 154 'file' => 'aggregator.pages.inc', 155 ); 156 $items['aggregator/rss'] = array( 157 'title' => 'RSS feed', 158 'page callback' => 'aggregator_page_rss', 159 'access arguments' => array('access news feeds'), 160 'type' => MENU_CALLBACK, 161 'file' => 'aggregator.pages.inc', 162 ); 163 $items['aggregator/opml'] = array( 164 'title' => 'OPML feed', 165 'page callback' => 'aggregator_page_opml', 166 'access arguments' => array('access news feeds'), 167 'type' => MENU_CALLBACK, 168 'file' => 'aggregator.pages.inc', 169 ); 170 $items['aggregator/categories/%aggregator_category'] = array( 171 'title callback' => '_aggregator_category_title', 172 'title arguments' => array(2), 173 'page callback' => 'aggregator_page_category', 174 'page arguments' => array(2), 175 'access callback' => 'user_access', 176 'access arguments' => array('access news feeds'), 177 'file' => 'aggregator.pages.inc', 178 ); 179 $items['aggregator/categories/%aggregator_category/view'] = array( 180 'title' => 'View', 181 'type' => MENU_DEFAULT_LOCAL_TASK, 182 'weight' => -10, 183 ); 184 $items['aggregator/categories/%aggregator_category/categorize'] = array( 185 'title' => 'Categorize', 186 'page callback' => 'drupal_get_form', 187 'page arguments' => array('aggregator_page_category', 2), 188 'access arguments' => array('administer news feeds'), 189 'type' => MENU_LOCAL_TASK, 190 'file' => 'aggregator.pages.inc', 191 ); 192 $items['aggregator/categories/%aggregator_category/configure'] = array( 193 'title' => 'Configure', 194 'page callback' => 'drupal_get_form', 195 'page arguments' => array('aggregator_form_category', 2), 196 'access arguments' => array('administer news feeds'), 197 'type' => MENU_LOCAL_TASK, 198 'weight' => 1, 199 'file' => 'aggregator.admin.inc', 200 ); 201 $items['aggregator/sources/%aggregator_feed'] = array( 202 'page callback' => 'aggregator_page_source', 203 'page arguments' => array(2), 204 'access arguments' => array('access news feeds'), 205 'type' => MENU_CALLBACK, 206 'file' => 'aggregator.pages.inc', 207 ); 208 $items['aggregator/sources/%aggregator_feed/view'] = array( 209 'title' => 'View', 210 'type' => MENU_DEFAULT_LOCAL_TASK, 211 'weight' => -10, 212 ); 213 $items['aggregator/sources/%aggregator_feed/categorize'] = array( 214 'title' => 'Categorize', 215 'page callback' => 'drupal_get_form', 216 'page arguments' => array('aggregator_page_source', 2), 217 'access arguments' => array('administer news feeds'), 218 'type' => MENU_LOCAL_TASK, 219 'file' => 'aggregator.pages.inc', 220 ); 221 $items['aggregator/sources/%aggregator_feed/configure'] = array( 222 'title' => 'Configure', 223 'page callback' => 'drupal_get_form', 224 'page arguments' => array('aggregator_form_feed', 2), 225 'access arguments' => array('administer news feeds'), 226 'type' => MENU_LOCAL_TASK, 227 'weight' => 1, 228 'file' => 'aggregator.admin.inc', 229 ); 230 $items['admin/content/aggregator/edit/feed/%aggregator_feed'] = array( 231 'title' => 'Edit feed', 232 'page callback' => 'drupal_get_form', 233 'page arguments' => array('aggregator_form_feed', 5), 234 'access arguments' => array('administer news feeds'), 235 'type' => MENU_CALLBACK, 236 'file' => 'aggregator.admin.inc', 237 ); 238 $items['admin/content/aggregator/edit/category/%aggregator_category'] = array( 239 'title' => 'Edit category', 240 'page callback' => 'drupal_get_form', 241 'page arguments' => array('aggregator_form_category', 5), 242 'access arguments' => array('administer news feeds'), 243 'type' => MENU_CALLBACK, 244 'file' => 'aggregator.admin.inc', 245 ); 246 247 return $items; 248 } 249 250 /** 251 * Menu callback. 252 * 253 * @return 254 * An aggregator category title. 255 */ 256 function _aggregator_category_title($category) { 257 return $category['title']; 258 } 259 260 /** 261 * Implementation of hook_init(). 262 */ 263 function aggregator_init() { 264 drupal_add_css(drupal_get_path('module', 'aggregator') .'/aggregator.css'); 265 } 266 267 /** 268 * Find out whether there are any aggregator categories. 269 * 270 * @return 271 * TRUE if there is at least one category and the user has access to them, FALSE otherwise. 272 */ 273 function _aggregator_has_categories() { 274 return user_access('access news feeds') && db_result(db_query('SELECT COUNT(*) FROM {aggregator_category}')); 275 } 276 277 /** 278 * Implementation of hook_perm(). 279 */ 280 function aggregator_perm() { 281 return array('administer news feeds', 'access news feeds'); 282 } 283 284 /** 285 * Implementation of hook_cron(). 286 * 287 * Checks news feeds for updates once their refresh interval has elapsed. 288 */ 289 function aggregator_cron() { 290 $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < %d', time()); 291 while ($feed = db_fetch_array($result)) { 292 aggregator_refresh($feed); 293 } 294 } 295 296 /** 297 * Implementation of hook_block(). 298 * 299 * Generates blocks for the latest news items in each category and feed. 300 */ 301 function aggregator_block($op = 'list', $delta = 0, $edit = array()) { 302 if (user_access('access news feeds')) { 303 if ($op == 'list') { 304 $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title'); 305 while ($category = db_fetch_object($result)) { 306 $block['category-'. $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title)); 307 } 308 $result = db_query('SELECT fid, title FROM {aggregator_feed} ORDER BY fid'); 309 while ($feed = db_fetch_object($result)) { 310 $block['feed-'. $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title)); 311 } 312 } 313 else if ($op == 'configure') { 314 list($type, $id) = explode('-', $delta); 315 if ($type == 'category') { 316 $value = db_result(db_query('SELECT block FROM {aggregator_category} WHERE cid = %d', $id)); 317 } 318 else { 319 $value = db_result(db_query('SELECT block FROM {aggregator_feed} WHERE fid = %d', $id)); 320 } 321 $form['block'] = array('#type' => 'select', '#title' => t('Number of news items in block'), '#default_value' => $value, '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20))); 322 return $form; 323 } 324 else if ($op == 'save') { 325 list($type, $id) = explode('-', $delta); 326 if ($type == 'category') { 327 $value = db_query('UPDATE {aggregator_category} SET block = %d WHERE cid = %d', $edit['block'], $id); 328 } 329 else { 330 $value = db_query('UPDATE {aggregator_feed} SET block = %d WHERE fid = %d', $edit['block'], $id); 331 } 332 } 333 else if ($op == 'view') { 334 list($type, $id) = explode('-', $delta); 335 switch ($type) { 336 case 'feed': 337 if ($feed = db_fetch_object(db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE fid = %d', $id))) { 338 $block['subject'] = check_plain($feed->title); 339 $result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = %d ORDER BY timestamp DESC, iid DESC', $feed->fid, 0, $feed->block); 340 $read_more = theme('more_link', url('aggregator/sources/'. $feed->fid), t("View this feed's recent news.")); 341 } 342 break; 343 344 case 'category': 345 if ($category = db_fetch_object(db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = %d', $id))) { 346 $block['subject'] = check_plain($category->title); 347 $result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = %d ORDER BY i.timestamp DESC, i.iid DESC', $category->cid, 0, $category->block); 348 $read_more = theme('more_link', url('aggregator/categories/'. $category->cid), t("View this category's recent news.")); 349 } 350 break; 351 } 352 $items = array(); 353 while ($item = db_fetch_object($result)) { 354 $items[] = theme('aggregator_block_item', $item); 355 } 356 357 // Only display the block if there are items to show. 358 if (count($items) > 0) { 359 $block['content'] = theme('item_list', $items) . $read_more; 360 } 361 } 362 if (isset($block)) { 363 return $block; 364 } 365 } 366 } 367 368 /** 369 * Add/edit/delete aggregator categories. 370 * 371 * @param $edit 372 * An associative array describing the category to be added/edited/deleted. 373 */ 374 function aggregator_save_category($edit) { 375 $link_path = 'aggregator/categories/'; 376 if (!empty($edit['cid'])) { 377 $link_path .= $edit['cid']; 378 if (!empty($edit['title'])) { 379 db_query("UPDATE {aggregator_category} SET title = '%s', description = '%s' WHERE cid = %d", $edit['title'], $edit['description'], $edit['cid']); 380 $op = 'update'; 381 } 382 else { 383 db_query('DELETE FROM {aggregator_category} WHERE cid = %d', $edit['cid']); 384 // Make sure there is no active block for this category. 385 db_query("DELETE FROM {blocks} WHERE module = '%s' AND delta = '%s'", 'aggregator', 'category-' . $edit['cid']); 386 $edit['title'] = ''; 387 $op = 'delete'; 388 } 389 } 390 else if (!empty($edit['title'])) { 391 // A single unique id for bundles and feeds, to use in blocks 392 db_query("INSERT INTO {aggregator_category} (title, description, block) VALUES ('%s', '%s', 5)", $edit['title'], $edit['description']); 393 $link_path .= db_last_insert_id('aggregator_category', 'cid'); 394 $op = 'insert'; 395 } 396 if (isset($op)) { 397 menu_link_maintain('aggregator', $op, $link_path, $edit['title']); 398 } 399 } 400 401 /** 402 * Add/edit/delete an aggregator feed. 403 * 404 * @param $edit 405 * An associative array describing the feed to be added/edited/deleted. 406 */ 407 function aggregator_save_feed($edit) { 408 if (!empty($edit['fid'])) { 409 // An existing feed is being modified, delete the category listings. 410 db_query('DELETE FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']); 411 } 412 if (!empty($edit['fid']) && !empty($edit['title'])) { 413 db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d", $edit['title'], $edit['url'], $edit['refresh'], $edit['fid']); 414 } 415 else if (!empty($edit['fid'])) { 416 $items = array(); 417 $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $edit['fid']); 418 while ($item = db_fetch_object($result)) { 419 $items[] = "iid = $item->iid"; 420 } 421 if (!empty($items)) { 422 db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items)); 423 } 424 db_query('DELETE FROM {aggregator_feed} WHERE fid = %d', $edit['fid']); 425 db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $edit['fid']); 426 // Make sure there is no active block for this feed. 427 db_query("DELETE FROM {blocks} WHERE module = '%s' AND delta = '%s'", 'aggregator', 'feed-' . $edit['fid']); 428 } 429 else if (!empty($edit['title'])) { 430 db_query("INSERT INTO {aggregator_feed} (title, url, refresh, block, description, image) VALUES ('%s', '%s', %d, 5, '', '')", $edit['title'], $edit['url'], $edit['refresh']); 431 // A single unique id for bundles and feeds, to use in blocks. 432 $edit['fid'] = db_last_insert_id('aggregator_feed', 'fid'); 433 } 434 if (!empty($edit['title'])) { 435 // The feed is being saved, save the categories as well. 436 if (!empty($edit['category'])) { 437 foreach ($edit['category'] as $cid => $value) { 438 if ($value) { 439 db_query('INSERT INTO {aggregator_category_feed} (fid, cid) VALUES (%d, %d)', $edit['fid'], $cid); 440 } 441 } 442 } 443 } 444 } 445 446 /** 447 * Removes all items from a feed. 448 * 449 * @param $feed 450 * An associative array describing the feed to be cleared. 451 */ 452 function aggregator_remove($feed) { 453 $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $feed['fid']); 454 while ($item = db_fetch_object($result)) { 455 $items[] = "iid = $item->iid"; 456 } 457 if (!empty($items)) { 458 db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items)); 459 } 460 db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $feed['fid']); 461 db_query("UPDATE {aggregator_feed} SET checked = 0, etag = '', modified = 0 WHERE fid = %d", $feed['fid']); 462 drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed['title']))); 463 } 464 465 /** 466 * Call-back function used by the XML parser. 467 */ 468 function aggregator_element_start($parser, $name, $attributes) { 469 global $item, $element, $tag, $items, $channel; 470 471 switch ($name) { 472 case 'IMAGE': 473 case 'TEXTINPUT': 474 case 'CONTENT': 475 case 'SUMMARY': 476 case 'TAGLINE': 477 case 'SUBTITLE': 478 case 'LOGO': 479 case 'INFO': 480 $element = $name; 481 break; 482 case 'ID': 483 if ($element != 'ITEM') { 484 $element = $name; 485 } 486 case 'LINK': 487 if (!empty($attributes['REL']) && $attributes['REL'] == 'alternate') { 488 if ($element == 'ITEM') { 489 $items[$item]['LINK'] = $attributes['HREF']; 490 } 491 else { 492 $channel['LINK'] = $attributes['HREF']; 493 } 494 } 495 break; 496 case 'ITEM': 497 $element = $name; 498 $item += 1; 499 break; 500 case 'ENTRY': 501 $element = 'ITEM'; 502 $item += 1; 503 break; 504 } 505 506 $tag = $name; 507 } 508 509 /** 510 * Call-back function used by the XML parser. 511 */ 512 function aggregator_element_end($parser, $name) { 513 global $element; 514 515 switch ($name) { 516 case 'IMAGE': 517 case 'TEXTINPUT': 518 case 'ITEM': 519 case 'ENTRY': 520 case 'CONTENT': 521 case 'INFO': 522 $element = ''; 523 break; 524 case 'ID': 525 if ($element == 'ID') { 526 $element = ''; 527 } 528 } 529 } 530 531 /** 532 * Call-back function used by the XML parser. 533 */ 534 function aggregator_element_data($parser, $data) { 535 global $channel, $element, $items, $item, $image, $tag; 536 $items += array($item => array()); 537 switch ($element) { 538 case 'ITEM': 539 $items[$item] += array($tag => ''); 540 $items[$item][$tag] .= $data; 541 break; 542 case 'IMAGE': 543 case 'LOGO': 544 $image += array($tag => ''); 545 $image[$tag] .= $data; 546 break; 547 case 'LINK': 548 if ($data) { 549 $items[$item] += array($tag => ''); 550 $items[$item][$tag] .= $data; 551 } 552 break; 553 case 'CONTENT': 554 $items[$item] += array('CONTENT' => ''); 555 $items[$item]['CONTENT'] .= $data; 556 break; 557 case 'SUMMARY': 558 $items[$item] += array('SUMMARY' => ''); 559 $items[$item]['SUMMARY'] .= $data; 560 break; 561 case 'TAGLINE': 562 case 'SUBTITLE': 563 $channel += array('DESCRIPTION' => ''); 564 $channel['DESCRIPTION'] .= $data; 565 break; 566 case 'INFO': 567 case 'ID': 568 case 'TEXTINPUT': 569 // The sub-element is not supported. However, we must recognize 570 // it or its contents will end up in the item array. 571 break; 572 default: 573 $channel += array($tag => ''); 574 $channel[$tag] .= $data; 575 } 576 } 577 578 /** 579 * Checks a news feed for new items. 580 * 581 * @param $feed 582 * An associative array describing the feed to be refreshed. 583 */ 584 function aggregator_refresh($feed) { 585 global $channel, $image; 586 587 // Generate conditional GET headers. 588 $headers = array(); 589 if ($feed['etag']) { 590 $headers['If-None-Match'] = $feed['etag']; 591 } 592 if ($feed['modified']) { 593 $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed['modified']) .' GMT'; 594 } 595 596 // Request feed. 597 $result = drupal_http_request($feed['url'], $headers); 598 599 // Process HTTP response code. 600 switch ($result->code) { 601 case 304: 602 db_query('UPDATE {aggregator_feed} SET checked = %d WHERE fid = %d', time(), $feed['fid']); 603 drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title']))); 604 break; 605 case 301: 606 $feed['url'] = $result->redirect_url; 607 watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed['title'], '%url' => $feed['url'])); 608 // Deliberate no break. 609 case 200: 610 case 302: 611 case 307: 612 // Filter the input data: 613 if (aggregator_parse_feed($result->data, $feed)) { 614 $modified = empty($result->headers['Last-Modified']) ? 0 : strtotime($result->headers['Last-Modified']); 615 616 // Prepare the channel data. 617 foreach ($channel as $key => $value) { 618 $channel[$key] = trim($value); 619 } 620 621 // Prepare the image data (if any). 622 foreach ($image as $key => $value) { 623 $image[$key] = trim($value); 624 } 625 626 if (!empty($image['LINK']) && !empty($image['URL']) && !empty($image['TITLE'])) { 627 // Note, we should really use theme_image() here but that only works with local images it won't work with images fetched with a URL unless PHP version > 5 628 $image = '<a href="'. check_url($image['LINK']) .'" class="feed-image"><img src="'. check_url($image['URL']) .'" alt="'. check_plain($image['TITLE']) .'" /></a>'; 629 } 630 else { 631 $image = NULL; 632 } 633 634 $etag = empty($result->headers['ETag']) ? '' : $result->headers['ETag']; 635 // Update the feed data. 636 db_query("UPDATE {aggregator_feed} SET url = '%s', checked = %d, link = '%s', description = '%s', image = '%s', etag = '%s', modified = %d WHERE fid = %d", $feed['url'], time(), $channel['LINK'], $channel['DESCRIPTION'], $image, $etag, $modified, $feed['fid']); 637 638 // Clear the cache. 639 cache_clear_all(); 640 641 watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed['title'])); 642 drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed['title']))); 643 } 644 break; 645 default: 646 watchdog('aggregator', 'The feed from %site seems to be broken, due to "%error".', array('%site' => $feed['title'], '%error' => $result->code .' '. $result->error), WATCHDOG_WARNING); 647 drupal_set_message(t('The feed from %site seems to be broken, because of error "%error".', array('%site' => $feed['title'], '%error' => $result->code .' '. $result->error))); 648 } 649 } 650 651 /** 652 * Parse the W3C date/time format, a subset of ISO 8601. PHP date parsing 653 * functions do not handle this format. 654 * See http://www.w3.org/TR/NOTE-datetime for more information. 655 * Originally from MagpieRSS (http://magpierss.sourceforge.net/). 656 * 657 * @param $date_str 658 * A string with a potentially W3C DTF date. 659 * @return 660 * A timestamp if parsed successfully or FALSE if not. 661 */ 662 function aggregator_parse_w3cdtf($date_str) { 663 if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) { 664 list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]); 665 // calc epoch for current date assuming GMT 666 $epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year); 667 if ($match[10] != 'Z') { // Z is zulu time, aka GMT 668 list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]); 669 // zero out the variables 670 if (!$tz_hour) { 671 $tz_hour = 0; 672 } 673 if (!$tz_min) { 674 $tz_min = 0; 675 } 676 $offset_secs = (($tz_hour * 60) + $tz_min) * 60; 677 // is timezone ahead of GMT? then subtract offset 678 if ($tz_mod == '+') { 679 $offset_secs *= -1; 680 } 681 $epoch += $offset_secs; 682 } 683 return $epoch; 684 } 685 else { 686 return FALSE; 687 } 688 } 689 690 /** 691 * Parse a feed and store its items. 692 * 693 * @param $data 694 * The feed data. 695 * @param $feed 696 * An associative array describing the feed to be parsed. 697 * @return 698 * 0 on error, 1 otherwise. 699 */ 700 function aggregator_parse_feed(&$data, $feed) { 701 global $items, $image, $channel; 702 703 // Unset the global variables before we use them: 704 unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']); 705 $items = array(); 706 $image = array(); 707 $channel = array(); 708 709 // parse the data: 710 $xml_parser = drupal_xml_parser_create($data); 711 xml_set_element_handler($xml_parser, 'aggregator_element_start', 'aggregator_element_end'); 712 xml_set_character_data_handler($xml_parser, 'aggregator_element_data'); 713 714 if (!xml_parse($xml_parser, $data, 1)) { 715 watchdog('aggregator', 'The feed from %site seems to be broken, due to an error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser)), WATCHDOG_WARNING); 716 drupal_set_message(t('The feed from %site seems to be broken, because of error "%error" on line %line.', array('%site' => $feed['title'], '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), 'error'); 717 return 0; 718 } 719 xml_parser_free($xml_parser); 720 721 // We reverse the array such that we store the first item last, and the last 722 // item first. In the database, the newest item should be at the top. 723 $items = array_reverse($items); 724 725 // Initialize variables. 726 $title = $link = $author = $description = $guid = NULL; 727 foreach ($items as $item) { 728 unset($title, $link, $author, $description, $guid); 729 730 // Prepare the item: 731 foreach ($item as $key => $value) { 732 $item[$key] = trim($value); 733 } 734 735 // Resolve the item's title. If no title is found, we use up to 40 736 // characters of the description ending at a word boundary but not 737 // splitting potential entities. 738 if (!empty($item['TITLE'])) { 739 $title = $item['TITLE']; 740 } 741 elseif (!empty($item['DESCRIPTION'])) { 742 $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['DESCRIPTION'], 40)); 743 } 744 else { 745 $title = ''; 746 } 747 748 // Resolve the items link. 749 if (!empty($item['LINK'])) { 750 $link = $item['LINK']; 751 } 752 else { 753 $link = $feed['link']; 754 } 755 $guid = isset($item['GUID']) ? $item['GUID'] : ''; 756 757 // Atom feeds have a CONTENT and/or SUMMARY tag instead of a DESCRIPTION tag. 758 if (!empty($item['CONTENT:ENCODED'])) { 759 $item['DESCRIPTION'] = $item['CONTENT:ENCODED']; 760 } 761 else if (!empty($item['SUMMARY'])) { 762 $item['DESCRIPTION'] = $item['SUMMARY']; 763 } 764 else if (!empty($item['CONTENT'])) { 765 $item['DESCRIPTION'] = $item['CONTENT']; 766 } 767 768 // Try to resolve and parse the item's publication date. If no date is 769 // found, we use the current date instead. 770 $date = 'now'; 771 foreach (array('PUBDATE', 'DC:DATE', 'DCTERMS:ISSUED', 'DCTERMS:CREATED', 'DCTERMS:MODIFIED', 'ISSUED', 'CREATED', 'MODIFIED', 'PUBLISHED', 'UPDATED') as $key) { 772 if (!empty($item[$key])) { 773 $date = $item[$key]; 774 break; 775 } 776 } 777 778 $timestamp = strtotime($date); // As of PHP 5.1.0, strtotime returns FALSE on failure instead of -1. 779 if ($timestamp <= 0) { 780 $timestamp = aggregator_parse_w3cdtf($date); // Returns FALSE on failure 781 if (!$timestamp) { 782 $timestamp = time(); // better than nothing 783 } 784 } 785 786 // Save this item. Try to avoid duplicate entries as much as possible. If 787 // we find a duplicate entry, we resolve it and pass along its ID is such 788 // that we can update it if needed. 789 if (!empty($guid)) { 790 $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND guid = '%s'", $feed['fid'], $guid)); 791 } 792 else if ($link && $link != $feed['link'] && $link != $feed['url']) { 793 $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND link = '%s'", $feed['fid'], $link)); 794 } 795 else { 796 $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND title = '%s'", $feed['fid'], $title)); 797 } 798 $item += array('AUTHOR' => '', 'DESCRIPTION' => ''); 799 aggregator_save_item(array('iid' => (isset($entry->iid) ? $entry->iid: ''), 'fid' => $feed['fid'], 'timestamp' => $timestamp, 'title' => $title, 'link' => $link, 'author' => $item['AUTHOR'], 'description' => $item['DESCRIPTION'], 'guid' => $guid)); 800 } 801 802 // Remove all items that are older than flush item timer. 803 $age = time() - variable_get('aggregator_clear', 9676800); 804 $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age); 805 806 $items = array(); 807 $num_rows = FALSE; 808 while ($item = db_fetch_object($result)) { 809 $items[] = $item->iid; 810 $num_rows = TRUE; 811 } 812 if ($num_rows) { 813 db_query('DELETE FROM {aggregator_category_item} WHERE iid IN ('. implode(', ', $items) .')'); 814 db_query('DELETE FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age); 815 } 816 817 return 1; 818 } 819 820 /** 821 * Add/edit/delete an aggregator item. 822 * 823 * @param $edit 824 * An associative array describing the item to be added/edited/deleted. 825 */ 826 function aggregator_save_item($edit) { 827 if ($edit['iid'] && $edit['title']) { 828 db_query("UPDATE {aggregator_item} SET title = '%s', link = '%s', author = '%s', description = '%s', guid = '%s', timestamp = %d WHERE iid = %d", $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['guid'], $edit['timestamp'], $edit['iid']); 829 } 830 else if ($edit['iid']) { 831 db_query('DELETE FROM {aggregator_item} WHERE iid = %d', $edit['iid']); 832 db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $edit['iid']); 833 } 834 else if ($edit['title'] && $edit['link']) { 835 db_query("INSERT INTO {aggregator_item} (fid, title, link, author, description, timestamp, guid) VALUES (%d, '%s', '%s', '%s', '%s', %d, '%s')", $edit['fid'], $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['timestamp'], $edit['guid']); 836 $edit['iid'] = db_last_insert_id('aggregator_item', 'iid'); 837 // file the items in the categories indicated by the feed 838 $categories = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']); 839 while ($category = db_fetch_object($categories)) { 840 db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $category->cid, $edit['iid']); 841 } 842 } 843 } 844 845 /** 846 * Load an aggregator feed. 847 * 848 * @param $fid 849 * The feed id. 850 * @return 851 * An associative array describing the feed. 852 */ 853 function aggregator_feed_load($fid) { 854 static $feeds; 855 if (!isset($feeds[$fid])) { 856 $feeds[$fid] = db_fetch_array(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', $fid)); 857 } 858 return $feeds[$fid]; 859 } 860 861 /** 862 * Load an aggregator category. 863 * 864 * @param $cid 865 * The category id. 866 * @return 867 * An associative array describing the category. 868 */ 869 function aggregator_category_load($cid) { 870 static $categories; 871 if (!isset($categories[$cid])) { 872 $categories[$cid] = db_fetch_array(db_query('SELECT * FROM {aggregator_category} WHERE cid = %d', $cid)); 873 } 874 return $categories[$cid]; 875 } 876 877 /** 878 * Format an individual feed item for display in the block. 879 * 880 * @param $item 881 * The item to be displayed. 882 * @param $feed 883 * Not used. 884 * @return 885 * The item HTML. 886 * @ingroup themeable 887 */ 888 function theme_aggregator_block_item($item, $feed = 0) { 889 global $user; 890 891 $output = ''; 892 if ($user->uid && module_exists('blog') && user_access('create blog entries')) { 893 if ($image = theme('image', 'misc/blog.png', t('blog it'), t('blog it'))) { 894 $output .= '<div class="icon">'. l($image, 'node/add/blog', array('attributes' => array('title' => t('Comment on this news item in your personal blog.'), 'class' => 'blog-it'), 'query' => "iid=$item->iid", 'html' => TRUE)) .'</div>'; 895 } 896 } 897 898 // Display the external link to the item. 899 $output .= '<a href="'. check_url($item->link) .'">'. check_plain($item->title) ."</a>\n"; 900 901 return $output; 902 } 903 904 /** 905 * Safely render HTML content, as allowed. 906 * 907 * @param $value 908 * The content to be filtered. 909 * @return 910 * The filtered content. 911 */ 912 function aggregator_filter_xss($value) { 913 return filter_xss($value, preg_split('/\s+|<|>/', variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), -1, PREG_SPLIT_NO_EMPTY)); 914 } 915 916 /** 917 * Helper function for drupal_map_assoc. 918 * 919 * @param $count 920 * Items count. 921 * @return 922 * Plural-formatted "@count items" 923 */ 924 function _aggregator_items($count) { 925 return format_plural($count, '1 item', '@count items'); 926 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Thu Mar 24 11:18:33 2011 | Cross-referenced by PHPXref 0.7 |