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