[ Index ]
 

Code source de Drupal 5.3

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

/modules/aggregator/ -> aggregator.module (source)

   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  }


Généré le : Fri Nov 30 16:20:15 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics