[ Index ] |
|
Code source de Drupal 5.3 |
1 <?php 2 // $Id: aggregator.module,v 1.324.2.1 2007/07/09 03:38:22 drumm 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($section) { 13 switch ($section) { 14 case 'admin/help#aggregator': 15 $output = '<p>'. t('The news aggregator is a powerful on-site RSS syndicator/news reader that can gather fresh content from news sites and weblogs around the web.') .'</p>'; 16 $output .= '<p>'. t('Users can view the latest news chronologically in the <a href="@aggregator">main news aggregator display</a> or by <a href="@aggregator-sources">source</a>. Administrators can add, edit and delete feeds and choose how often to check for newly updated news for each individual feed. Administrators can also tag individual feeds with categories, offering selective grouping of some feeds into separate displays. Listings of the latest news for individual sources or categorized sources can be enabled as blocks for display in the sidebar through the <a href="@admin-block">block administration page</a>. The news aggregator requires cron to check for the latest news from the sites to which you have subscribed. Drupal also provides a <a href="@aggregator-opml">machine-readable OPML file</a> of all of your subscribed feeds.', array('@aggregator' => url('aggregator'), '@aggregator-sources' => url('aggregator/sources'), '@admin-block' => url('admin/build/block'), '@aggregator-opml' => url('aggregator/opml'))) .'</p>'; 17 $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@aggregator">Aggregator page</a>.', array('@aggregator' => 'http://drupal.org/handbook/modules/aggregator/')) .'</p>'; 18 return $output; 19 case 'admin/content/aggregator': 20 return '<p>'. t('Thousands of sites (particularly news sites and weblogs) publish their latest headlines and/or stories in a machine-readable format so that other sites can easily link to them. This content is usually in the form of an <a href="http://blogs.law.harvard.edu/tech/rss">RSS</a> feed (which is an XML-based syndication standard). To display the feed or category in a block you must decide how many items to show by editing the feed or block and turning on the <a href="@block">feed\'s block</a>.', array('@block' => url('admin/build/block'))) .'</p>'; 21 case 'admin/content/aggregator/add/feed': 22 return '<p>'. t('Add a site that has an RSS/RDF/Atom feed. The URL is the full path to the feed file. For the feed to update automatically you must run "cron.php" on a regular basis. If you already have a feed with the URL you are planning to use, the system will not accept another feed with the same URL.') .'</p>'; 23 case 'admin/content/aggregator/add/category': 24 return '<p>'. t('Categories provide a way to group items from different news feeds together. Each news category has its own feed page and block. For example, you could tag various sport-related feeds as belonging to a category called <em>Sports</em>. News items can be added to a category automatically by setting a feed to automatically place its item into that category, or by using the categorize items link in any listing of news items.') .'</p>'; 25 } 26 } 27 28 /** 29 * Implementation of hook_menu(). 30 */ 31 function aggregator_menu($may_cache) { 32 $items = array(); 33 $edit = user_access('administer news feeds'); 34 $view = user_access('access news feeds'); 35 36 if ($may_cache) { 37 $items[] = array('path' => 'admin/content/aggregator', 38 'title' => t('News aggregator'), 39 'description' => t("Configure which content your site aggregates from other sites, how often it polls them, and how they're categorized."), 40 'callback' => 'aggregator_admin_overview', 41 'access' => $edit); 42 $items[] = array('path' => 'admin/content/aggregator/add/feed', 43 'title' => t('Add feed'), 44 'callback' => 'drupal_get_form', 45 'callback arguments' => array('aggregator_form_feed'), 46 'access' => $edit, 47 'type' => MENU_LOCAL_TASK); 48 $items[] = array('path' => 'admin/content/aggregator/add/category', 49 'title' => t('Add category'), 50 'callback' => 'drupal_get_form', 51 'callback arguments' => array('aggregator_form_category'), 52 'access' => $edit, 53 'type' => MENU_LOCAL_TASK); 54 $items[] = array('path' => 'admin/content/aggregator/remove', 55 'title' => t('Remove items'), 56 'callback' => 'aggregator_admin_remove_feed', 57 'access' => $edit, 58 'type' => MENU_CALLBACK); 59 $items[] = array('path' => 'admin/content/aggregator/update', 60 'title' => t('Update items'), 61 'callback' => 'aggregator_admin_refresh_feed', 62 'access' => $edit, 63 'type' => MENU_CALLBACK); 64 $items[] = array('path' => 'admin/content/aggregator/list', 65 'title' => t('List'), 66 'type' => MENU_DEFAULT_LOCAL_TASK, 67 'weight' => -10); 68 $items[] = array('path' => 'admin/content/aggregator/settings', 69 'title' => t('Settings'), 70 'callback' => 'drupal_get_form', 71 'callback arguments' => array('aggregator_admin_settings'), 72 'type' => MENU_LOCAL_TASK, 73 'weight' => 10, 74 'access' => $edit); 75 76 $items[] = array('path' => 'aggregator', 77 'title' => t('News aggregator'), 78 'callback' => 'aggregator_page_last', 79 'access' => $view, 80 'weight' => 5); 81 $items[] = array('path' => 'aggregator/sources', 82 'title' => t('Sources'), 83 'callback' => 'aggregator_page_sources', 84 'access' => $view); 85 $items[] = array('path' => 'aggregator/categories', 86 'title' => t('Categories'), 87 'callback' => 'aggregator_page_categories', 88 'access' => $view, 89 'type' => MENU_ITEM_GROUPING); 90 $items[] = array('path' => 'aggregator/rss', 91 'title' => t('RSS feed'), 92 'callback' => 'aggregator_page_rss', 93 'access' => $view, 94 'type' => MENU_CALLBACK); 95 $items[] = array('path' => 'aggregator/opml', 96 'title' => t('OPML feed'), 97 'callback' => 'aggregator_page_opml', 98 'access' => $view, 99 'type' => MENU_CALLBACK); 100 101 $result = db_query('SELECT title, cid FROM {aggregator_category} ORDER BY title'); 102 while ($category = db_fetch_array($result)) { 103 $items[] = array('path' => 'aggregator/categories/'. $category['cid'], 104 'title' => $category['title'], 105 'callback' => 'aggregator_page_category', 106 'access' => $view); 107 } 108 } 109 else { 110 // Add the CSS for this module 111 // We put this in !$may_cache so it's only added once per request 112 drupal_add_css(drupal_get_path('module', 'aggregator') .'/aggregator.css'); 113 114 if (arg(0) == 'aggregator' && is_numeric(arg(2))) { 115 if (arg(1) == 'sources') { 116 $feed = aggregator_get_feed(arg(2)); 117 if ($feed) { 118 $items[] = array('path' => 'aggregator/sources/'. $feed['fid'], 119 'title' => $feed['title'], 120 'callback' => 'aggregator_page_source', 121 'access' => $view, 122 'type' => MENU_CALLBACK); 123 $items[] = array('path' => 'aggregator/sources/'. $feed['fid'] .'/view', 124 'title' => t('View'), 125 'type' => MENU_DEFAULT_LOCAL_TASK, 126 'weight' => -10); 127 $items[] = array('path' => 'aggregator/sources/'. $feed['fid'] .'/categorize', 128 'title' => t('Categorize'), 129 'callback' => 'drupal_get_form', 130 'callback arguments' => array('aggregator_page_source'), 131 'access' => $edit, 132 'type' => MENU_LOCAL_TASK); 133 $items[] = array('path' => 'aggregator/sources/'. $feed['fid'] .'/configure', 134 'title' => t('Configure'), 135 'callback' => 'drupal_get_form', 136 'callback arguments' => array('aggregator_form_feed', $feed), 137 'access' => $edit, 138 'type' => MENU_LOCAL_TASK, 139 'weight' => 1); 140 } 141 } 142 else if (arg(1) == 'categories') { 143 $category = aggregator_get_category(arg(2)); 144 if ($category) { 145 $items[] = array('path' => 'aggregator/categories/'. $category['cid'] .'/view', 146 'title' => t('View'), 147 'type' => MENU_DEFAULT_LOCAL_TASK, 148 'weight' => -10); 149 $items[] = array('path' => 'aggregator/categories/'. $category['cid'] .'/categorize', 150 'title' => t('Categorize'), 151 'callback' => 'drupal_get_form', 152 'callback arguments' => array('aggregator_page_category'), 153 'access' => $edit, 154 'type' => MENU_LOCAL_TASK); 155 $items[] = array('path' => 'aggregator/categories/'. $category['cid'] .'/configure', 156 'title' => t('Configure'), 157 'callback' => 'drupal_get_form', 158 'callback arguments' => array('aggregator_form_category', $category), 159 'access' => $edit, 160 'type' => MENU_LOCAL_TASK, 161 'weight' => 1); 162 } 163 } 164 } 165 else if (arg(2) == 'aggregator' && is_numeric(arg(5))) { 166 if (arg(4) == 'feed') { 167 $feed = aggregator_get_feed(arg(5)); 168 if ($feed) { 169 $items[] = array('path' => 'admin/content/aggregator/edit/feed/'. $feed['fid'], 170 'title' => t('Edit feed'), 171 'callback' => 'drupal_get_form', 172 'callback arguments' => array('aggregator_form_feed', $feed), 173 'access' => $edit, 174 'type' => MENU_CALLBACK); 175 } 176 } 177 else { 178 $category = aggregator_get_category(arg(5)); 179 if ($category) { 180 $items[] = array('path' => 'admin/content/aggregator/edit/category/'. $category['cid'], 181 'title' => t('Edit category'), 182 'callback' => 'drupal_get_form', 183 'callback arguments' => array('aggregator_form_category', $category), 184 'access' => $edit, 185 'type' => MENU_CALLBACK); 186 } 187 } 188 } 189 } 190 191 return $items; 192 } 193 194 function aggregator_admin_settings() { 195 $items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items'); 196 $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval'); 197 198 $form['aggregator_allowed_html_tags'] = array( 199 '#type' => 'textfield', '#title' => t('Allowed HTML tags'), '#size' => 80, '#maxlength' => 255, 200 '#default_value' => variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), 201 '#description' => t('The list of tags which are allowed in feeds, i.e., which will not be removed by Drupal.') 202 ); 203 204 $form['aggregator_summary_items'] = array( 205 '#type' => 'select', '#title' => t('Items shown in sources and categories pages') , 206 '#default_value' => variable_get('aggregator_summary_items', 3), '#options' => $items, 207 '#description' => t('The number of items which will be shown with each feed or category in the feed and category summary pages.') 208 ); 209 210 $form['aggregator_clear'] = array( 211 '#type' => 'select', '#title' => t('Discard news items older than'), 212 '#default_value' => variable_get('aggregator_clear', 9676800), '#options' => $period, 213 '#description' => t('Older news items will be automatically discarded. Requires crontab.') 214 ); 215 216 $form['aggregator_category_selector'] = array( 217 '#type' => 'radios', '#title' => t('Category selection type'), '#default_value' => variable_get('aggregator_category_selector', 'checkboxes'), 218 '#options' => array('checkboxes' => t('checkboxes'), 'select' => t('multiple selector')), 219 '#description' => t('The type of category selection widget which is shown on categorization pages. Checkboxes are easier to use; a multiple selector is good for working with large numbers of categories.') 220 ); 221 222 return system_settings_form($form); 223 } 224 225 /** 226 * Implementation of hook_perm(). 227 */ 228 function aggregator_perm() { 229 return array('administer news feeds', 'access news feeds'); 230 } 231 232 /** 233 * Implementation of hook_cron(). 234 * 235 * Checks news feeds for updates once their refresh interval has elapsed. 236 */ 237 function aggregator_cron() { 238 $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < %d', time()); 239 while ($feed = db_fetch_array($result)) { 240 aggregator_refresh($feed); 241 } 242 } 243 244 /** 245 * Implementation of hook_block(). 246 * 247 * Generates blocks for the latest news items in each category and feed. 248 */ 249 function aggregator_block($op = 'list', $delta = 0, $edit = array()) { 250 if (user_access('access news feeds')) { 251 if ($op == 'list') { 252 $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title'); 253 while ($category = db_fetch_object($result)) { 254 $block['category-'. $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title)); 255 } 256 $result = db_query('SELECT fid, title FROM {aggregator_feed} ORDER BY fid'); 257 while ($feed = db_fetch_object($result)) { 258 $block['feed-'. $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title)); 259 } 260 } 261 else if ($op == 'configure') { 262 list($type, $id) = explode('-', $delta); 263 if ($type == 'category') { 264 $value = db_result(db_query('SELECT block FROM {aggregator_category} WHERE cid = %d', $id)); 265 } 266 else { 267 $value = db_result(db_query('SELECT block FROM {aggregator_feed} WHERE fid = %d', $id)); 268 } 269 $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))); 270 return $form; 271 } 272 else if ($op == 'save') { 273 list($type, $id) = explode('-', $delta); 274 if ($type == 'category') { 275 $value = db_query('UPDATE {aggregator_category} SET block = %d WHERE cid = %d', $edit['block'], $id); 276 } 277 else { 278 $value = db_query('UPDATE {aggregator_feed} SET block = %d WHERE fid = %d', $edit['block'], $id); 279 } 280 } 281 else if ($op == 'view') { 282 list($type, $id) = explode('-', $delta); 283 switch ($type) { 284 case 'feed': 285 if ($feed = db_fetch_object(db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE fid = %d', $id))) { 286 $block['subject'] = check_plain($feed->title); 287 $result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = %d ORDER BY timestamp DESC, iid DESC', $feed->fid, 0, $feed->block); 288 $read_more = '<div class="more-link">'. l(t('more'), 'aggregator/sources/'. $feed->fid, array('title' => t("View this feed's recent news."))) .'</div>'; 289 } 290 break; 291 292 case 'category': 293 if ($category = db_fetch_object(db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = %d', $id))) { 294 $block['subject'] = check_plain($category->title); 295 $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); 296 $read_more = '<div class="more-link">'. l(t('more'), 'aggregator/categories/'. $category->cid, array('title' => t("View this category's recent news."))) .'</div>'; 297 } 298 break; 299 } 300 $items = array(); 301 while ($item = db_fetch_object($result)) { 302 $items[] = theme('aggregator_block_item', $item); 303 } 304 305 // Only display the block if there are items to show. 306 if (count($items) > 0) { 307 $block['content'] = theme('item_list', $items) . $read_more; 308 } 309 } 310 return $block; 311 } 312 } 313 314 /** 315 * Generate a form to add/edit/delete aggregator categories. 316 */ 317 function aggregator_form_category($edit = array()) { 318 $form['title'] = array('#type' => 'textfield', 319 '#title' => t('Title'), 320 '#default_value' => $edit['title'], 321 '#maxlength' => 64, 322 '#required' => TRUE, 323 ); 324 $form['description'] = array('#type' => 'textarea', 325 '#title' => t('Description'), 326 '#default_value' => $edit['description'], 327 ); 328 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit')); 329 330 if ($edit['cid']) { 331 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete')); 332 $form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']); 333 } 334 335 return $form; 336 } 337 338 /** 339 * Validate aggregator_form_feed form submissions. 340 */ 341 function aggregator_form_category_validate($form_id, $form_values) { 342 if ($form_values['op'] == t('Submit')) { 343 // Check for duplicate titles 344 if (isset($form_values['cid'])) { 345 $category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s' AND cid != %d", $form_values['title'], $form_values['cid'])); 346 } 347 else { 348 $category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s'", $form_values['title'])); 349 } 350 if ($category) { 351 form_set_error('title', t('A category named %category already exists. Please enter a unique title.', array('%category' => $form_values['title']))); 352 } 353 } 354 } 355 356 /** 357 * Process aggregator_form_category form submissions. 358 * @todo Add delete confirmation dialog. 359 */ 360 function aggregator_form_category_submit($form_id, $form_values) { 361 if ($form_values['op'] == t('Delete')) { 362 $title = $form_values['title']; 363 // Unset the title: 364 unset($form_values['title']); 365 } 366 aggregator_save_category($form_values); 367 menu_rebuild(); 368 if (isset($form_values['cid'])) { 369 if (isset($form_values['title'])) { 370 drupal_set_message(t('The category %category has been updated.', array('%category' => $form_values['title']))); 371 if (arg(0) == 'admin') { 372 return 'admin/content/aggregator/'; 373 } 374 else { 375 return 'aggregator/categories/'. $form_values['cid']; 376 } 377 } 378 else { 379 watchdog('aggregator', t('Category %category deleted.', array('%category' => $title))); 380 drupal_set_message(t('The category %category has been deleted.', array('%category' => $title))); 381 if (arg(0) == 'admin') { 382 return 'admin/content/aggregator/'; 383 } 384 else { 385 return 'aggregator/categories/'; 386 } 387 } 388 } 389 else { 390 watchdog('aggregator', t('Category %category added.', array('%category' => $form_values['title'])), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator')); 391 drupal_set_message(t('The category %category has been added.', array('%category' => $form_values['title']))); 392 } 393 } 394 395 /** 396 * Add/edit/delete aggregator categories. 397 */ 398 function aggregator_save_category($edit) { 399 if ($edit['cid'] && $edit['title']) { 400 db_query("UPDATE {aggregator_category} SET title = '%s', description = '%s' WHERE cid = %d", $edit['title'], $edit['description'], $edit['cid']); 401 } 402 else if ($edit['cid']) { 403 db_query('DELETE FROM {aggregator_category} WHERE cid = %d', $edit['cid']); 404 } 405 else if ($edit['title']) { 406 // A single unique id for bundles and feeds, to use in blocks 407 $next_id = db_next_id('{aggregator_category}_cid'); 408 db_query("INSERT INTO {aggregator_category} (cid, title, description, block) VALUES (%d, '%s', '%s', 5)", $next_id, $edit['title'], $edit['description']); 409 } 410 } 411 412 /** 413 * Generate a form to add/edit feed sources. 414 */ 415 function aggregator_form_feed($edit = array()) { 416 $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval'); 417 418 if ($edit['refresh'] == '') { 419 $edit['refresh'] = 3600; 420 } 421 422 $form['title'] = array('#type' => 'textfield', 423 '#title' => t('Title'), 424 '#default_value' => $edit['title'], 425 '#maxlength' => 255, 426 '#description' => t('The name of the feed; typically the name of the web site you syndicate content from.'), 427 '#required' => TRUE, 428 ); 429 $form['url'] = array('#type' => 'textfield', 430 '#title' => t('URL'), 431 '#default_value' => $edit['url'], 432 '#maxlength' => 255, 433 '#description' => t('The fully-qualified URL of the feed.'), 434 '#required' => TRUE, 435 ); 436 $form['refresh'] = array('#type' => 'select', 437 '#title' => t('Update interval'), 438 '#default_value' => $edit['refresh'], 439 '#options' => $period, 440 '#description' => t('The refresh interval indicating how often you want to update this feed. Requires crontab.'), 441 ); 442 443 // Handling of categories: 444 $options = array(); 445 $values = array(); 446 $categories = db_query('SELECT c.cid, c.title, f.fid FROM {aggregator_category} c LEFT JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = %d ORDER BY title', $edit['fid']); 447 while ($category = db_fetch_object($categories)) { 448 $options[$category->cid] = check_plain($category->title); 449 if ($category->fid) $values[] = $category->cid; 450 } 451 if ($options) { 452 $form['category'] = array('#type' => 'checkboxes', 453 '#title' => t('Categorize news items'), 454 '#default_value' => $values, 455 '#options' => $options, 456 '#description' => t('New items in this feed will be automatically filed in the checked categories as they are received.'), 457 ); 458 } 459 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit')); 460 461 if ($edit['fid']) { 462 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete')); 463 $form['fid'] = array('#type' => 'hidden', '#value' => $edit['fid']); 464 } 465 466 return $form; 467 } 468 469 /** 470 * Validate aggregator_form_feed form submissions. 471 */ 472 function aggregator_form_feed_validate($form_id, $form_values) { 473 if ($form_values['op'] == t('Submit')) { 474 // Check for duplicate titles 475 if (isset($form_values['fid'])) { 476 $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE (title = '%s' OR url='%s') AND fid != %d", $form_values['title'], $form_values['url'], $form_values['fid']); 477 } 478 else { 479 $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = '%s' OR url='%s'", $form_values['title'], $form_values['url']); 480 } 481 while ($feed = db_fetch_object($result)) { 482 if (strcasecmp($feed->title, $form_values['title']) == 0) { 483 form_set_error('title', t('A feed named %feed already exists. Please enter a unique title.', array('%feed' => $form_values['title']))); 484 } 485 if (strcasecmp($feed->url, $form_values['url']) == 0) { 486 form_set_error('url', t('A feed with this URL %url already exists. Please enter a unique URL.', array('%url' => $form_values['url']))); 487 } 488 } 489 } 490 } 491 492 /** 493 * Process aggregator_form_feed form submissions. 494 * @todo Add delete confirmation dialog. 495 */ 496 function aggregator_form_feed_submit($form_id, $form_values) { 497 if ($form_values['op'] == t('Delete')) { 498 $title = $form_values['title']; 499 // Unset the title: 500 unset($form_values['title']); 501 } 502 aggregator_save_feed($form_values); 503 menu_rebuild(); 504 if (isset($form_values['fid'])) { 505 if (isset($form_values['title'])) { 506 drupal_set_message(t('The feed %feed has been updated.', array('%feed' => $form_values['title']))); 507 if (arg(0) == 'admin') { 508 return 'admin/content/aggregator/'; 509 } 510 else { 511 return 'aggregator/sources/'. $form_values['fid']; 512 } 513 } 514 else { 515 watchdog('aggregator', t('Feed %feed deleted.', array('%feed' => $title))); 516 drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => $title))); 517 if (arg(0) == 'admin') { 518 return 'admin/content/aggregator/'; 519 } 520 else { 521 return 'aggregator/sources/'; 522 } 523 } 524 } 525 else { 526 watchdog('aggregator', t('Feed %feed added.', array('%feed' => $form_values['title'])), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator')); 527 drupal_set_message(t('The feed %feed has been added.', array('%feed' => $form_values['title']))); 528 } 529 } 530 531 /** 532 * Add/edit/delete an aggregator feed. 533 */ 534 function aggregator_save_feed($edit) { 535 if ($edit['fid']) { 536 // An existing feed is being modified, delete the category listings. 537 db_query('DELETE FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']); 538 } 539 if ($edit['fid'] && $edit['title']) { 540 db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d", $edit['title'], $edit['url'], $edit['refresh'], $edit['fid']); 541 } 542 else if ($edit['fid']) { 543 $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $edit['fid']); 544 while ($item = db_fetch_object($result)) { 545 $items[] = "iid = $item->iid"; 546 } 547 if ($items) { 548 db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items)); 549 } 550 db_query('DELETE FROM {aggregator_feed} WHERE fid = %d', $edit['fid']); 551 db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $edit['fid']); 552 } 553 else if ($edit['title']) { 554 // A single unique id for bundles and feeds, to use in blocks. 555 $edit['fid'] = db_next_id('{aggregator_feed}_fid'); 556 db_query("INSERT INTO {aggregator_feed} (fid, title, url, refresh, block) VALUES (%d, '%s', '%s', %d, 5)", $edit['fid'], $edit['title'], $edit['url'], $edit['refresh']); 557 } 558 if ($edit['title']) { 559 // The feed is being saved, save the categories as well. 560 if ($edit['category']) { 561 foreach ($edit['category'] as $cid => $value) { 562 if ($value) { 563 db_query('INSERT INTO {aggregator_category_feed} (fid, cid) VALUES (%d, %d)', $edit['fid'], $cid); 564 } 565 } 566 } 567 } 568 } 569 570 function aggregator_remove($feed) { 571 $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $feed['fid']); 572 while ($item = db_fetch_object($result)) { 573 $items[] = "iid = $item->iid"; 574 } 575 if ($items) { 576 db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items)); 577 } 578 db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $feed['fid']); 579 db_query("UPDATE {aggregator_feed} SET checked = 0, etag = '', modified = 0 WHERE fid = %d", $feed['fid']); 580 drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed['title']))); 581 } 582 583 /** 584 * Call-back function used by the XML parser. 585 */ 586 function aggregator_element_start($parser, $name, $attributes) { 587 global $item, $element, $tag, $items, $channel; 588 589 switch ($name) { 590 case 'IMAGE': 591 case 'TEXTINPUT': 592 case 'CONTENT': 593 case 'SUMMARY': 594 case 'TAGLINE': 595 case 'SUBTITLE': 596 case 'LOGO': 597 case 'INFO': 598 $element = $name; 599 break; 600 case 'ID': 601 if ($element != 'ITEM') { 602 $element = $name; 603 } 604 case 'LINK': 605 if ($attributes['REL'] == 'alternate') { 606 if ($element == 'ITEM') { 607 $items[$item]['LINK'] = $attributes['HREF']; 608 } 609 else { 610 $channel['LINK'] = $attributes['HREF']; 611 } 612 } 613 break; 614 case 'ITEM': 615 $element = $name; 616 $item += 1; 617 break; 618 case 'ENTRY': 619 $element = 'ITEM'; 620 $item += 1; 621 break; 622 } 623 624 $tag = $name; 625 } 626 627 /** 628 * Call-back function used by the XML parser. 629 */ 630 function aggregator_element_end($parser, $name) { 631 global $element; 632 633 switch ($name) { 634 case 'IMAGE': 635 case 'TEXTINPUT': 636 case 'ITEM': 637 case 'ENTRY': 638 case 'CONTENT': 639 case 'INFO': 640 $element = ''; 641 break; 642 case 'ID': 643 if ($element == 'ID') { 644 $element = ''; 645 } 646 } 647 } 648 649 /** 650 * Call-back function used by the XML parser. 651 */ 652 function aggregator_element_data($parser, $data) { 653 global $channel, $element, $items, $item, $image, $tag; 654 switch ($element) { 655 case 'ITEM': 656 $items[$item][$tag] .= $data; 657 break; 658 case 'IMAGE': 659 case 'LOGO': 660 $image[$tag] .= $data; 661 break; 662 case 'LINK': 663 if ($data) { 664 $items[$item][$tag] .= $data; 665 } 666 break; 667 case 'CONTENT': 668 $items[$item]['CONTENT'] .= $data; 669 break; 670 case 'SUMMARY': 671 $items[$item]['SUMMARY'] .= $data; 672 break; 673 case 'TAGLINE': 674 case 'SUBTITLE': 675 $channel['DESCRIPTION'] .= $data; 676 break; 677 case 'INFO': 678 case 'ID': 679 case 'TEXTINPUT': 680 // The sub-element is not supported. However, we must recognize 681 // it or its contents will end up in the item array. 682 break; 683 default: 684 $channel[$tag] .= $data; 685 } 686 } 687 688 /** 689 * Checks a news feed for new items. 690 */ 691 function aggregator_refresh($feed) { 692 global $channel, $image; 693 694 // Generate conditional GET headers. 695 $headers = array(); 696 if ($feed['etag']) { 697 $headers['If-None-Match'] = $feed['etag']; 698 } 699 if ($feed['modified']) { 700 $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed['modified']) .' GMT'; 701 } 702 703 // Request feed. 704 $result = drupal_http_request($feed['url'], $headers); 705 706 // Process HTTP response code. 707 switch ($result->code) { 708 case 304: 709 db_query('UPDATE {aggregator_feed} SET checked = %d WHERE fid = %d', time(), $feed['fid']); 710 drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title']))); 711 break; 712 case 301: 713 $feed['url'] = $result->redirect_url; 714 watchdog('aggregator', t('Updated URL for feed %title to %url.', array('%title' => $feed['title'], '%url' => $feed['url']))); 715 716 case 200: 717 case 302: 718 case 307: 719 // Filter the input data: 720 if (aggregator_parse_feed($result->data, $feed)) { 721 722 if ($result->headers['Last-Modified']) { 723 $modified = strtotime($result->headers['Last-Modified']); 724 } 725 726 /* 727 ** Prepare the channel data: 728 */ 729 730 foreach ($channel as $key => $value) { 731 $channel[$key] = trim($value); 732 } 733 734 /* 735 ** Prepare the image data (if any): 736 */ 737 738 foreach ($image as $key => $value) { 739 $image[$key] = trim($value); 740 } 741 742 if ($image['LINK'] && $image['URL'] && $image['TITLE']) { 743 // 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 744 $image = '<a href="'. check_url($image['LINK']) .'" class="feed-image"><img src="'. check_url($image['URL']) .'" alt="'. check_plain($image['TITLE']) .'" /></a>'; 745 } 746 else { 747 $image = NULL; 748 } 749 750 /* 751 ** Update the feed data: 752 */ 753 754 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, $result->headers['ETag'], $modified, $feed['fid']); 755 756 /* 757 ** Clear the cache: 758 */ 759 760 cache_clear_all(); 761 762 watchdog('aggregator', t('There is new syndicated content from %site.', array('%site' => $feed['title']))); 763 drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed['title']))); 764 } 765 break; 766 default: 767 watchdog('aggregator', t('The feed from %site seems to be broken, due to "%error".', array('%site' => $feed['title'], '%error' => $result->code .' '. $result->error)), WATCHDOG_WARNING); 768 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))); 769 } 770 } 771 772 /** 773 * Parse the W3C date/time format, a subset of ISO 8601. PHP date parsing 774 * functions do not handle this format. 775 * See http://www.w3.org/TR/NOTE-datetime for more information. 776 * Originally from MagpieRSS (http://magpierss.sourceforge.net/). 777 * 778 * @param $date_str A string with a potentially W3C DTF date. 779 * @return A timestamp if parsed successfully or -1 if not. 780 */ 781 function aggregator_parse_w3cdtf($date_str) { 782 if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) { 783 list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]); 784 // calc epoch for current date assuming GMT 785 $epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year); 786 if ($match[10] != 'Z') { // Z is zulu time, aka GMT 787 list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]); 788 // zero out the variables 789 if (!$tz_hour) { 790 $tz_hour = 0; 791 } 792 if (!$tz_min) { 793 $tz_min = 0; 794 } 795 $offset_secs = (($tz_hour * 60) + $tz_min) * 60; 796 // is timezone ahead of GMT? then subtract offset 797 if ($tz_mod == '+') { 798 $offset_secs *= -1; 799 } 800 $epoch += $offset_secs; 801 } 802 return $epoch; 803 } 804 else { 805 return FALSE; 806 } 807 } 808 809 function aggregator_parse_feed(&$data, $feed) { 810 global $items, $image, $channel; 811 812 // Unset the global variables before we use them: 813 unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']); 814 $items = array(); 815 $image = array(); 816 $channel = array(); 817 818 // parse the data: 819 $xml_parser = drupal_xml_parser_create($data); 820 xml_set_element_handler($xml_parser, 'aggregator_element_start', 'aggregator_element_end'); 821 xml_set_character_data_handler($xml_parser, 'aggregator_element_data'); 822 823 if (!xml_parse($xml_parser, $data, 1)) { 824 watchdog('aggregator', t('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); 825 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'); 826 return 0; 827 } 828 xml_parser_free($xml_parser); 829 830 /* 831 ** We reverse the array such that we store the first item last, 832 ** and the last item first. In the database, the newest item 833 ** should be at the top. 834 */ 835 836 $items = array_reverse($items); 837 838 // Initialize variables 839 $title = $link = $author = $description = $guid = NULL; 840 foreach ($items as $item) { 841 unset($title, $link, $author, $description, $guid); 842 843 // Prepare the item: 844 foreach ($item as $key => $value) { 845 $item[$key] = trim($value); 846 } 847 848 /* 849 ** Resolve the item's title. If no title is found, we use 850 ** up to 40 characters of the description ending at a word 851 ** boundary but not splitting potential entities. 852 */ 853 854 if ($item['TITLE']) { 855 $title = $item['TITLE']; 856 } 857 else { 858 $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['DESCRIPTION'], 40)); 859 } 860 861 /* 862 ** Resolve the items link. 863 */ 864 865 if ($item['LINK']) { 866 $link = $item['LINK']; 867 } 868 else { 869 $link = $feed['link']; 870 } 871 if ($item['GUID']) { 872 $guid = $item['GUID']; 873 } 874 875 /** 876 * Atom feeds have a CONTENT and/or SUMMARY tag instead of a DESCRIPTION tag 877 */ 878 if ($item['CONTENT:ENCODED']) { 879 $item['DESCRIPTION'] = $item['CONTENT:ENCODED']; 880 } 881 else if ($item['SUMMARY']) { 882 $item['DESCRIPTION'] = $item['SUMMARY']; 883 } 884 else if ($item['CONTENT']) { 885 $item['DESCRIPTION'] = $item['CONTENT']; 886 } 887 888 /* 889 ** Try to resolve and parse the item's publication date. If no 890 ** date is found, we use the current date instead. 891 */ 892 893 if ($item['PUBDATE']) $date = $item['PUBDATE']; // RSS 2.0 894 else if ($item['DC:DATE']) $date = $item['DC:DATE']; // Dublin core 895 else if ($item['DCTERMS:ISSUED']) $date = $item['DCTERMS:ISSUED']; // Dublin core 896 else if ($item['DCTERMS:CREATED']) $date = $item['DCTERMS:CREATED']; // Dublin core 897 else if ($item['DCTERMS:MODIFIED']) $date = $item['DCTERMS:MODIFIED']; // Dublin core 898 else if ($item['ISSUED']) $date = $item['ISSUED']; // Atom XML 899 else if ($item['CREATED']) $date = $item['CREATED']; // Atom XML 900 else if ($item['MODIFIED']) $date = $item['MODIFIED']; // Atom XML 901 else if ($item['PUBLISHED']) $date = $item['PUBLISHED']; // Atom XML 902 else if ($item['UPDATED']) $date = $item['UPDATED']; // Atom XML 903 else $date = 'now'; 904 905 $timestamp = strtotime($date); // As of PHP 5.1.0, strtotime returns FALSE on failure instead of -1. 906 if ($timestamp <= 0) { 907 $timestamp = aggregator_parse_w3cdtf($date); // Returns FALSE on failure 908 if (!$timestamp) { 909 $timestamp = time(); // better than nothing 910 } 911 } 912 913 /* 914 ** Save this item. Try to avoid duplicate entries as much as 915 ** possible. If we find a duplicate entry, we resolve it and 916 ** pass along it's ID such that we can update it if needed. 917 */ 918 919 if ($guid) { 920 $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND guid = '%s'", $feed['fid'], $guid)); 921 } 922 else if ($link && $link != $feed['link'] && $link != $feed['url']) { 923 $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND link = '%s'", $feed['fid'], $link)); 924 } 925 else { 926 $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND title = '%s'", $feed['fid'], $title)); 927 } 928 929 aggregator_save_item(array('iid' => $entry->iid, 'fid' => $feed['fid'], 'timestamp' => $timestamp, 'title' => $title, 'link' => $link, 'author' => $item['AUTHOR'], 'description' => $item['DESCRIPTION'], 'guid' => $guid)); 930 } 931 932 /* 933 ** Remove all items that are older than flush item timer: 934 */ 935 936 $age = time() - variable_get('aggregator_clear', 9676800); 937 $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age); 938 939 if (db_num_rows($result)) { 940 $items = array(); 941 while ($item = db_fetch_object($result)) { 942 $items[] = $item->iid; 943 } 944 db_query('DELETE FROM {aggregator_category_item} WHERE iid IN ('. implode(', ', $items) .')'); 945 db_query('DELETE FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age); 946 } 947 948 return 1; 949 } 950 951 function aggregator_save_item($edit) { 952 if ($edit['iid'] && $edit['title']) { 953 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']); 954 } 955 else if ($edit['iid']) { 956 db_query('DELETE FROM {aggregator_item} WHERE iid = %d', $edit['iid']); 957 db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $edit['iid']); 958 } 959 else if ($edit['title'] && $edit['link']) { 960 $edit['iid'] = db_next_id('{aggregator_item}_iid'); 961 db_query("INSERT INTO {aggregator_item} (iid, fid, title, link, author, description, timestamp, guid) VALUES (%d, %d, '%s', '%s', '%s', '%s', %d, '%s')", $edit['iid'], $edit['fid'], $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['timestamp'], $edit['guid']); 962 // file the items in the categories indicated by the feed 963 $categories = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']); 964 while ($category = db_fetch_object($categories)) { 965 db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $category->cid, $edit['iid']); 966 } 967 } 968 } 969 970 function aggregator_get_feed($fid) { 971 return db_fetch_array(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', $fid)); 972 } 973 974 function aggregator_get_category($cid) { 975 return db_fetch_array(db_query('SELECT * FROM {aggregator_category} WHERE cid = %d', $cid)); 976 } 977 978 function aggregator_view() { 979 $result = db_query('SELECT f.*, COUNT(i.iid) AS items FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.etag, f.modified, f.image, f.block ORDER BY f.title'); 980 981 $output = '<h3>'. t('Feed overview') .'</h3>'; 982 983 $header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3')); 984 $rows = array(); 985 while ($feed = db_fetch_object($result)) { 986 $rows[] = array(l($feed->title, "aggregator/sources/$feed->fid"), format_plural($feed->items, '1 item', '@count items'), ($feed->checked ? t('@time ago', array('@time' => format_interval(time() - $feed->checked))) : t('never')), ($feed->checked ? t('%time left', array('%time' => format_interval($feed->checked + $feed->refresh - time()))) : t('never')), l(t('edit'), "admin/content/aggregator/edit/feed/$feed->fid"), l(t('remove items'), "admin/content/aggregator/remove/$feed->fid"), l(t('update items'), "admin/content/aggregator/update/$feed->fid")); 987 } 988 $output .= theme('table', $header, $rows); 989 990 $result = db_query('SELECT c.cid, c.title, count(ci.iid) as items FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid GROUP BY c.cid, c.title ORDER BY title'); 991 992 $output .= '<h3>'. t('Category overview') .'</h3>'; 993 994 $header = array(t('Title'), t('Items'), t('Operations')); 995 $rows = array(); 996 while ($category = db_fetch_object($result)) { 997 $rows[] = array(l($category->title, "aggregator/categories/$category->cid"), format_plural($category->items, '1 item', '@count items'), l(t('edit'), "admin/content/aggregator/edit/category/$category->cid")); 998 } 999 $output .= theme('table', $header, $rows); 1000 1001 return $output; 1002 } 1003 1004 /** 1005 * Menu callback; removes all items from a feed, then redirects to the overview page. 1006 */ 1007 function aggregator_admin_remove_feed($feed) { 1008 aggregator_remove(aggregator_get_feed($feed)); 1009 drupal_goto('admin/content/aggregator'); 1010 } 1011 1012 /** 1013 * Menu callback; refreshes a feed, then redirects to the overview page. 1014 */ 1015 function aggregator_admin_refresh_feed($feed) { 1016 aggregator_refresh(aggregator_get_feed($feed)); 1017 drupal_goto('admin/content/aggregator'); 1018 } 1019 1020 /** 1021 * Menu callback; displays the aggregator administration page. 1022 */ 1023 function aggregator_admin_overview() { 1024 return aggregator_view(); 1025 } 1026 1027 /** 1028 * Menu callback; displays the most recent items gathered from any feed. 1029 */ 1030 function aggregator_page_last() { 1031 drupal_add_feed(url('aggregator/rss'), variable_get('site_name', 'Drupal') . ' ' . t('aggregator')); 1032 1033 return _aggregator_page_list('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC', arg(1)); 1034 } 1035 1036 /** 1037 * Menu callback; displays all the items captured from a particular feed. 1038 */ 1039 function aggregator_page_source() { 1040 $feed = db_fetch_object(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', arg(2))); 1041 $info = theme('aggregator_feed', $feed); 1042 1043 return _aggregator_page_list('SELECT * FROM {aggregator_item} WHERE fid = '. $feed->fid .' ORDER BY timestamp DESC, iid DESC', arg(3), $info); 1044 } 1045 1046 /** 1047 * Menu callback; displays all the items aggregated in a particular category. 1048 */ 1049 function aggregator_page_category() { 1050 $category = db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2))); 1051 1052 drupal_add_feed(url('aggregator/rss/'. arg(2)), variable_get('site_name', 'Drupal') . ' ' . t('aggregator - @title', array('@title' => $category->title))); 1053 1054 return _aggregator_page_list('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = '. $category->cid .' ORDER BY timestamp DESC, iid DESC', arg(3)); 1055 } 1056 1057 function aggregator_page_list($sql, $header, $categorize) { 1058 $form['#base'] = 'aggregator_page_list'; 1059 $form['header'] = array('#value' => $header); 1060 $result = pager_query($sql, 20); 1061 $categories = array(); 1062 $done = FALSE; 1063 $form['items'] = array(); 1064 $form['categories'] = array('#tree' => TRUE); 1065 while ($item = db_fetch_object($result)) { 1066 $form['items'][$item->iid] = array('#value' => theme('aggregator_page_item', $item)); 1067 $form['categories'][$item->iid] = array(); 1068 if ($categorize) { 1069 $categories_result = db_query('SELECT c.cid, c.title, ci.iid FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid AND ci.iid = %d', $item->iid); 1070 $selected = array(); 1071 while ($category = db_fetch_object($categories_result)) { 1072 if (!$done) { 1073 $categories[$category->cid] = check_plain($category->title); 1074 } 1075 if ($category->iid) { 1076 $selected[] = $category->cid; 1077 } 1078 } 1079 $done = TRUE; 1080 $form['categories'][$item->iid] = array( 1081 '#type' => variable_get('aggregator_category_selector', 'checkboxes'), 1082 '#default_value' => $selected, '#options' => $categories, 1083 '#size' => 10, '#multiple' => TRUE 1084 ); 1085 } 1086 } 1087 $form['submit'] = array('#type' => 'submit', '#value' => t('Save categories')); 1088 $form['pager'] = array('#value' => theme('pager', NULL, 20, 0)); 1089 1090 return $form; 1091 } 1092 1093 /** 1094 * Prints an aggregator page listing a number of feed items. Various 1095 * menu callbacks use this function to print their feeds. 1096 */ 1097 function _aggregator_page_list($sql, $op, $header = '') { 1098 $categorize = (user_access('administer news feeds') && ($op == 'categorize')); 1099 $form = aggregator_page_list($sql, $header, $categorize); 1100 if ($categorize) { 1101 return $form; 1102 } 1103 else { 1104 $output = '<div id="aggregator">'; 1105 $output .= $header; 1106 foreach ($form['items'] as $item) { 1107 $output .= $item['#value']; 1108 } 1109 $output .= '</div>'; 1110 $output .= $form['pager']['#value']; 1111 $output .= $form['feed_icon']['#value']; 1112 return $output; 1113 } 1114 } 1115 1116 function theme_aggregator_page_list($form) { 1117 $output = '<div id="aggregator">'; 1118 $output .= drupal_render($form['header']); 1119 $rows = array(); 1120 if ($form['items']) { 1121 foreach (element_children($form['items']) as $key) { 1122 if (is_array($form['items'][$key])) { 1123 $rows[] = array(drupal_render($form['items'][$key]), array('data' => drupal_render($form['categories'][$key]), 'class' => 'categorize-item')); 1124 } 1125 } 1126 } 1127 $output .= theme('table', array('', t('Categorize')), $rows); 1128 $output .= drupal_render($form['submit']); 1129 $output .= '</div>'; 1130 $output .= drupal_render($form); 1131 return $output; 1132 } 1133 1134 function aggregator_page_list_validate($form_id, &$form) { 1135 if (!user_access('administer news feeds')) { 1136 form_error($form, t('You are not allowed to categorize this feed item.')); 1137 } 1138 } 1139 1140 function aggregator_page_list_submit($form_id, $form_values) { 1141 foreach ($form_values['categories'] as $iid => $selection) { 1142 db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $iid); 1143 foreach ($selection as $cid) { 1144 if ($cid) { 1145 db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $cid, $iid); 1146 } 1147 } 1148 } 1149 drupal_set_message(t('The categories have been saved.')); 1150 } 1151 1152 /** 1153 * Menu callback; displays all the feeds used by the aggregator. 1154 */ 1155 function aggregator_page_sources() { 1156 $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title'); 1157 $output = "<div id=\"aggregator\">\n"; 1158 while ($feed = db_fetch_object($result)) { 1159 $output .= '<h2>'. check_plain($feed->title) ."</h2>\n"; 1160 1161 // Most recent items: 1162 $list = array(); 1163 if (variable_get('aggregator_summary_items', 3)) { 1164 $items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = %d ORDER BY i.timestamp DESC', $feed->fid, 0, variable_get('aggregator_summary_items', 3)); 1165 while ($item = db_fetch_object($items)) { 1166 $list[] = theme('aggregator_summary_item', $item); 1167 } 1168 } 1169 $output .= theme('item_list', $list); 1170 1171 $link['sources'] = array( 1172 'title' => t('More'), 1173 'href' => 'aggregator/sources/'. $feed->fid 1174 ); 1175 1176 $output .= '<div class="links">'. theme('links', $link) ."</div>\n"; 1177 } 1178 $output .= theme('xml_icon', url('aggregator/opml')); 1179 $output .= '</div>'; 1180 return $output; 1181 } 1182 1183 /** 1184 * Menu callback; generate an RSS 0.92 feed of aggregator items or categories. 1185 */ 1186 function aggregator_page_rss() { 1187 // arg(2) is the passed cid, only select for that category 1188 $result = NULL; 1189 if (arg(2)) { 1190 $category = db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2))); 1191 $url = '/categories/' . $category->cid; 1192 $title = ' ' . t('in category') . ' ' . $category->title; 1193 $sql = 'SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = %d ORDER BY timestamp DESC, iid DESC'; 1194 $result = db_query_range($sql, $category->cid, 0, variable_get('feed_default_items', 10)); 1195 } 1196 // or, get the default aggregator items 1197 else { 1198 $sql = 'SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC'; 1199 $result = db_query_range($sql, 0, variable_get('feed_default_items', 10)); 1200 } 1201 1202 while ($item = db_fetch_object($result)) { 1203 switch (variable_get('feed_item_length', 'teaser')) { 1204 case 'teaser': 1205 $teaser = node_teaser($item->description); 1206 if ($teaser != $item->description) { 1207 $teaser .= '<p><a href="'. check_url($item->link) .'">'. t('read more') ."</a></p>\n"; 1208 } 1209 $item->description = $teaser; 1210 break; 1211 case 'title': 1212 $item->description = ''; 1213 break; 1214 } 1215 $items .= format_rss_item($item->ftitle . ': ' . $item->title, $item->link, $item->description, array('pubDate' => date('r', $item->timestamp))); 1216 } 1217 1218 $output .= "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 1219 $output .= "<rss version=\"2.0\">\n"; 1220 $output .= format_rss_channel(variable_get('site_name', 'Drupal') . ' ' . t('aggregator'), url('aggregator' . $url, NULL, NULL, TRUE), variable_get('site_name', 'Drupal') . ' - ' . t('aggregated feeds') . $title, $items, 'en'); 1221 $output .= "</rss>\n"; 1222 1223 drupal_set_header('Content-Type: application/rss+xml; charset=utf-8'); 1224 print $output; 1225 } 1226 1227 /** 1228 * Menu callback; generates an OPML representation of all feeds. 1229 */ 1230 function aggregator_page_opml($cid = NULL) { 1231 if ($cid) { 1232 $result = db_query('SELECT f.title, f.url FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} c on f.fid = c.fid WHERE c.cid = %d ORDER BY title', $cid); 1233 } 1234 else { 1235 $result = db_query('SELECT * FROM {aggregator_feed} ORDER BY title'); 1236 } 1237 1238 $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; 1239 $output .= "<opml version=\"1.1\">\n"; 1240 $output .= "<head>\n"; 1241 $output .= '<title>'. check_plain(variable_get('site_name', 'Drupal')) ."</title>\n"; 1242 $output .= '<dateModified>'. gmdate('r') ."</dateModified>\n"; 1243 $output .= "</head>\n"; 1244 $output .= "<body>\n"; 1245 1246 while ($feed = db_fetch_object($result)) { 1247 $output .= '<outline text="'. check_plain($feed->title) .'" xmlUrl="'. check_url($feed->url) ."\" />\n"; 1248 } 1249 1250 $output .= "</body>\n"; 1251 $output .= "</opml>\n"; 1252 1253 drupal_set_header('Content-Type: text/xml; charset=utf-8'); 1254 print $output; 1255 } 1256 1257 /** 1258 * Menu callback; displays all the categories used by the aggregator. 1259 */ 1260 function aggregator_page_categories() { 1261 $result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description'); 1262 $output = "<div id=\"aggregator\">\n"; 1263 1264 while ($category = db_fetch_object($result)) { 1265 $output .= '<h2>'. check_plain($category->title) ."</h2>\n"; 1266 if (variable_get('aggregator_summary_items', 3)) { 1267 $list = array(); 1268 $items = db_query_range('SELECT i.title, i.timestamp, i.link, f.title as feed_title, f.link as feed_link FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON i.iid = ci.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE ci.cid = %d ORDER BY i.timestamp DESC', $category->cid, 0, variable_get('aggregator_summary_items', 3)); 1269 while ($item = db_fetch_object($items)) { 1270 $list[] = theme('aggregator_summary_item', $item); 1271 } 1272 $output .= theme('item_list', $list); 1273 } 1274 1275 $link['categories'] = array( 1276 'title' => t('More'), 1277 'href' => 'aggregator/categories/'. $category->cid 1278 ); 1279 1280 $output .= '<div class="links">'. theme('links', $link) ."</div>\n"; 1281 } 1282 1283 $output .= '</div>'; 1284 1285 return $output; 1286 } 1287 1288 /** 1289 * Format a news feed. 1290 * 1291 * @ingroup themeable 1292 */ 1293 function theme_aggregator_feed($feed) { 1294 $output = '<div class="feed-source">'; 1295 $output .= theme('feed_icon', $feed->url) ."\n"; 1296 $output .= $feed->image; 1297 $output .= '<div class="feed-description">'. aggregator_filter_xss($feed->description) ."</div>\n"; 1298 $output .= '<div class="feed-url"><em>'. t('URL:') .'</em> '. l($feed->link, $feed->link, array(), NULL, NULL, TRUE) ."</div>\n"; 1299 1300 if ($feed->checked) { 1301 $updated = t('@time ago', array('@time' => format_interval(time() - $feed->checked))); 1302 } 1303 else { 1304 $updated = t('never'); 1305 } 1306 1307 if (user_access('administer news feeds')) { 1308 $updated = l($updated, 'admin/content/aggregator'); 1309 } 1310 1311 $output .= '<div class="feed-updated"><em>'. t('Updated:') . "</em> $updated</div>"; 1312 $output .= "</div>\n"; 1313 1314 return $output; 1315 } 1316 1317 /** 1318 * Format an individual feed item for display in the block. 1319 * 1320 * @ingroup themeable 1321 */ 1322 function theme_aggregator_block_item($item, $feed = 0) { 1323 global $user; 1324 1325 if ($user->uid && module_exists('blog') && user_access('edit own blog')) { 1326 if ($image = theme('image', 'misc/blog.png', t('blog it'), t('blog it'))) { 1327 $output .= '<div class="icon">'. l($image, 'node/add/blog', array('title' => t('Comment on this news item in your personal blog.'), 'class' => 'blog-it'), "iid=$item->iid", NULL, FALSE, TRUE) .'</div>'; 1328 } 1329 } 1330 1331 // Display the external link to the item. 1332 $output .= '<a href="'. check_url($item->link) .'">'. check_plain($item->title) ."</a>\n"; 1333 1334 return $output; 1335 } 1336 1337 /** 1338 * Return a themed item heading for summary pages located at "aggregator/sources" 1339 * and "aggregator/categories". 1340 * 1341 * @param $item The item object from the aggregator module. 1342 * @return A string containing the output. 1343 * 1344 * @ingroup themeable 1345 */ 1346 function theme_aggregator_summary_item($item) { 1347 $output = '<a href="'. check_url($item->link) .'">'. check_plain($item->title) .'</a> <span class="age">'. t('%age old', array('%age' => format_interval(time() - $item->timestamp))) .'</span>'; 1348 if ($item->feed_link) { 1349 $output .= ', <span class="source"><a href="'. check_url($item->feed_link) .'">'. check_plain($item->feed_title) .'</a></span>'; 1350 } 1351 return $output ."\n"; 1352 } 1353 1354 /** 1355 * Format an individual feed item for display on the aggregator page. 1356 * 1357 * @ingroup themeable 1358 */ 1359 function theme_aggregator_page_item($item) { 1360 1361 $source = ''; 1362 if ($item->ftitle && $item->fid) { 1363 $source = l($item->ftitle, "aggregator/sources/$item->fid", array('class' => 'feed-item-source')) . ' -'; 1364 } 1365 1366 if (date('Ymd', $item->timestamp) == date('Ymd')) { 1367 $source_date = t('%ago ago', array('%ago' => format_interval(time() - $item->timestamp))); 1368 } 1369 else { 1370 $source_date = format_date($item->timestamp, 'custom', variable_get('date_format_medium', 'D, m/d/Y - H:i')); 1371 } 1372 1373 $output .= "<div class=\"feed-item\">\n"; 1374 $output .= '<h3 class="feed-item-title"><a href="'. check_url($item->link) .'">'. check_plain($item->title) ."</a></h3>\n"; 1375 $output .= "<div class=\"feed-item-meta\">$source <span class=\"feed-item-date\">$source_date</span></div>\n"; 1376 1377 if ($item->description) { 1378 $output .= '<div class="feed-item-body">'. aggregator_filter_xss($item->description) ."</div>\n"; 1379 } 1380 1381 $result = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = %d ORDER BY c.title', $item->iid); 1382 $categories = array(); 1383 while ($category = db_fetch_object($result)) { 1384 $categories[] = l($category->title, 'aggregator/categories/'. $category->cid); 1385 } 1386 if ($categories) { 1387 $output .= '<div class="feed-item-categories">'. t('Categories') .': '. implode(', ', $categories) ."</div>\n"; 1388 } 1389 1390 $output .= "</div>\n"; 1391 1392 return $output; 1393 } 1394 1395 /** 1396 * Safely render HTML content, as allowed. 1397 */ 1398 function aggregator_filter_xss($value) { 1399 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)); 1400 } 1401 1402 /** 1403 * Helper function for drupal_map_assoc. 1404 */ 1405 function _aggregator_items($count) { 1406 return format_plural($count, '1 item', '@count items'); 1407 }
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Fri Nov 30 16:20:15 2007 | par Balluche grâce à PHPXref 0.7 |
![]() |