'),
'#description' => t('The list of tags which are allowed in feeds, i.e., which will not be removed by Drupal.')
);
$form['aggregator_summary_items'] = array(
'#type' => 'select', '#title' => t('Items shown in sources and categories pages') ,
'#default_value' => variable_get('aggregator_summary_items', 3), '#options' => $items,
'#description' => t('The number of items which will be shown with each feed or category in the feed and category summary pages.')
);
$form['aggregator_clear'] = array(
'#type' => 'select', '#title' => t('Discard news items older than'),
'#default_value' => variable_get('aggregator_clear', 9676800), '#options' => $period,
'#description' => t('Older news items will be automatically discarded. Requires crontab.')
);
$form['aggregator_category_selector'] = array(
'#type' => 'radios', '#title' => t('Category selection type'), '#default_value' => variable_get('aggregator_category_selector', 'check'),
'#options' => array('checkboxes' => t('checkboxes'), 'select' => t('multiple selector')),
'#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.')
);
return $form;
}
/**
* Implementation of hook_perm().
*/
function aggregator_perm() {
return array('administer news feeds', 'access news feeds');
}
/**
* Implementation of hook_cron().
*
* Checks news feeds for updates once their refresh interval has elapsed.
*/
function aggregator_cron() {
$result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < %d', time());
while ($feed = db_fetch_array($result)) {
aggregator_refresh($feed);
}
}
/**
* Implementation of hook_block().
*
* Generates blocks for the latest news items in each category and feed.
*/
function aggregator_block($op, $delta = 0, $edit = array()) {
if (user_access('access news feeds')) {
if ($op == 'list') {
$result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title');
while ($category = db_fetch_object($result)) {
$block['category-'. $category->cid]['info'] = t('%title category latest items', array('%title' => theme('placeholder', $category->title)));
}
$result = db_query('SELECT fid, title FROM {aggregator_feed} ORDER BY fid');
while ($feed = db_fetch_object($result)) {
$block['feed-'. $feed->fid]['info'] = t('%title feed latest items', array('%title' => theme('placeholder', $feed->title)));
}
}
else if ($op == 'configure') {
list($type, $id) = explode('-', $delta);
if ($type == 'category') {
$value = db_result(db_query('SELECT block FROM {aggregator_category} WHERE cid = %d', $id));
}
else {
$value = db_result(db_query('SELECT block FROM {aggregator_feed} WHERE fid = %d', $id));
}
$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)));
return $form;
}
else if ($op == 'save') {
list($type, $id) = explode('-', $delta);
if ($type == 'category') {
$value = db_query('UPDATE {aggregator_category} SET block = %d WHERE cid = %d', $edit['block'], $id);
}
else {
$value = db_query('UPDATE {aggregator_feed} SET block = %d WHERE fid = %d', $edit['block'], $id);
}
}
else if ($op == 'view') {
list($type, $id) = explode('-', $delta);
switch ($type) {
case 'feed':
if ($feed = db_fetch_object(db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE fid = %d', $id))) {
$block['subject'] = check_plain($feed->title);
$result = db_query_range('SELECT * FROM {aggregator_item} WHERE fid = %d ORDER BY timestamp DESC, iid DESC', $feed->fid, 0, $feed->block);
$block['content'] = ''. l(t('more'), 'aggregator/sources/'. $feed->fid, array('title' => t('View this feed\'s recent news.'))) .'
';
}
break;
case 'category':
if ($category = db_fetch_object(db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = %d', $id))) {
$block['subject'] = check_plain($category->title);
$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);
$block['content'] = ''. l(t('more'), 'aggregator/categories/'. $category->cid, array('title' => t('View this category\'s recent news.'))) .'
';
}
break;
}
$items = array();
while ($item = db_fetch_object($result)) {
$items[] = theme('aggregator_block_item', $item);
}
$block['content'] = theme('item_list', $items) . $block['content'];
}
return $block;
}
}
/**
* Generate a form to add/edit/delete aggregator categories.
*/
function aggregator_form_category($edit = array()) {
$form['title'] = array('#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $edit['title'],
'#maxlength' => 64,
'#required' => TRUE,
);
$form['description'] = array('#type' => 'textarea',
'#title' => t('Description'),
'#default_value' => $edit['description'],
);
$form['submit'] = array('#type' => 'submit', '#value' =>t('Submit'));
if ($edit['cid']) {
$form['delete'] = array('#type' => 'submit', '#value' =>t('Delete'));
$form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']);
}
return drupal_get_form('aggregator_form_category', $form);
}
/**
* Validate aggregator_form_feed form submissions.
*/
function aggregator_form_category_validate($form_id, $form_values) {
if ($_POST['op'] == t('Submit')) {
// Check for duplicate titles
if (isset($form_values['cid'])) {
$category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s' AND cid != %d", $form_values['title'], $form_values['cid']));
}
else {
$category = db_fetch_object(db_query("SELECT cid FROM {aggregator_category} WHERE title = '%s'", $form_values['title']));
}
if ($category) {
form_set_error('title', t('A category named %category already exists. Please enter a unique title.', array('%category' => theme('placeholder', $form_values['title']))));
}
}
}
/**
* Process aggregator_form_category form submissions.
* @todo Add delete confirmation dialog.
*/
function aggregator_form_category_submit($form_id, $form_values) {
if ($_POST['op'] == t('Delete')) {
$title = $form_values['title'];
// Unset the title:
unset($form_values['title']);
}
aggregator_save_category($form_values);
menu_rebuild();
if (isset($form_values['cid'])) {
if (isset($form_values['title'])) {
drupal_set_message(t('The category %category has been updated.', array('%category' => theme('placeholder', $form_values['title']))));
if (arg(0) == 'admin') {
return 'admin/aggregator/';
}
else {
return 'aggregator/categories/'. $form_values['cid'];
}
}
else {
watchdog('aggregator', t('Category %category deleted.', array('%category' => theme('placeholder', $title))));
drupal_set_message(t('The category %category has been deleted.', array('%category' => theme('placeholder', $title))));
if (arg(0) == 'admin') {
return 'admin/aggregator/';
}
else {
return 'aggregator/categories/';
}
}
}
else {
watchdog('aggregator', t('Category %category added.', array('%category' => theme('placeholder', $form_values['title']))), WATCHDOG_NOTICE, l(t('view'), 'admin/aggregator'));
drupal_set_message(t('The category %category has been added.', array('%category' => theme('placeholder', $form_values['title']))));
}
}
/**
* Add/edit/delete aggregator categories.
*/
function aggregator_save_category($edit) {
if ($edit['cid'] && $edit['title']) {
db_query("UPDATE {aggregator_category} SET title = '%s', description = '%s' WHERE cid = %d", $edit['title'], $edit['description'], $edit['cid']);
}
else if ($edit['cid']) {
db_query('DELETE FROM {aggregator_category} WHERE cid = %d', $edit['cid']);
}
else if ($edit['title']) {
// A single unique id for bundles and feeds, to use in blocks
$next_id = db_next_id('{aggregator_category}_cid');
db_query("INSERT INTO {aggregator_category} (cid, title, description, block) VALUES (%d, '%s', '%s', 5)", $next_id, $edit['title'], $edit['description']);
}
}
/**
* Generate a form to add/edit feed sources.
*/
function aggregator_form_feed($edit = array()) {
$period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
if ($edit['refresh'] == '') {
$edit['refresh'] = 3600;
}
$form['title'] = array('#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $edit['title'],
'#maxlength' => 64,
'#description' => t('The name of the feed; typically the name of the web site you syndicate content from.'),
'#required' => TRUE,
);
$form['url'] = array('#type' => 'textfield',
'#title' => t('URL'),
'#default_value' => $edit['url'],
'#maxlength' => 255,
'#description' => t('The fully-qualified URL of the feed.'),
'#required' => TRUE,
);
$form['refresh'] = array('#type' => 'select',
'#title' => t('Update interval'),
'#default_value' => $edit['refresh'],
'#options' => $period,
'#description' => t('The refresh interval indicating how often you want to update this feed. Requires crontab.'),
);
// Handling of categories:
$options = array();
$values = array();
$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']);
while ($category = db_fetch_object($categories)) {
$options[$category->cid] = $category->title;
if ($category->fid) $values[] = check_plain($category->cid);
}
if ($options) {
$form['category'] = array('#type' => 'checkboxes',
'#title' => t('Categorize news items'),
'#default_value' => $values,
'#options' => $options,
'#description' => t('New items in this feed will be automatically filed in the checked categories as they are received.'),
);
}
$form['submit'] = array('#type' => 'submit', '#value' =>t('Submit'));
if ($edit['fid']) {
$form['delete'] = array('#type' => 'submit', '#value' =>t('Delete'));
$form['fid'] = array('#type' => 'hidden', '#value' => $edit['fid']);
}
return drupal_get_form('aggregator_form_feed', $form);
}
/**
* Validate aggregator_form_feed form submissions.
*/
function aggregator_form_feed_validate($form_id, $form_values) {
if ($_POST['op'] == t('Submit')) {
// Check for duplicate titles
if (isset($form_values['fid'])) {
$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']);
}
else {
$result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = '%s' OR url='%s'", $form_values['title'], $form_values['url']);
}
while ($feed = db_fetch_object($result)) {
if (strcasecmp($feed->title, $form_values['title']) == 0) {
form_set_error('title', t('A feed named %feed already exists. Please enter a unique title.', array('%feed' => theme('placeholder', $form_values['title']))));
}
}
}
}
/**
* Process aggregator_form_feed form submissions.
* @todo Add delete confirmation dialog.
*/
function aggregator_form_feed_submit($form_id, $form_values) {
if ($_POST['op'] == t('Delete')) {
$title = $form_values['title'];
// Unset the title:
unset($form_values['title']);
}
aggregator_save_feed($form_values);
menu_rebuild();
if (isset($form_values['fid'])) {
if (isset($form_values['title'])) {
drupal_set_message(t('The feed %feed has been updated.', array('%feed' => theme('placeholder', $form_values['title']))));
if (arg(0) == 'admin') {
return 'admin/aggregator/';
}
else {
return 'aggregator/sources/'. $form_values['fid'];
}
}
else {
watchdog('aggregator', t('Feed %feed deleted.', array('%feed' => theme('placeholder', $title))));
drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => theme('placeholder', $title))));
if (arg(0) == 'admin') {
return 'admin/aggregator/';
}
else {
return 'aggregator/sources/';
}
}
}
else {
watchdog('aggregator', t('Feed %feed added.', array('%feed' => theme('placeholder', $form_values['title']))), WATCHDOG_NOTICE, l(t('view'), 'admin/aggregator'));
drupal_set_message(t('The feed %feed has been added.', array('%feed' => theme('placeholder', $form_values['title']))));
}
}
/**
* Add/edit/delete an aggregator feed.
*/
function aggregator_save_feed($edit) {
if ($edit['fid']) {
// An existing feed is being modified, delete the category listings.
db_query('DELETE FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']);
}
if ($edit['fid'] && $edit['title']) {
db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d", $edit['title'], $edit['url'], $edit['refresh'], $edit['fid']);
}
else if ($edit['fid']) {
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
while ($item = db_fetch_object($result)) {
$items[] = "iid = $item->iid";
}
if ($items) {
db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items));
}
db_query('DELETE FROM {aggregator_feed} WHERE fid = %d', $edit['fid']);
db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
}
else if ($edit['title']) {
// A single unique id for bundles and feeds, to use in blocks.
$edit['fid'] = db_next_id('{aggregator_feed}_fid');
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']);
}
if ($edit['title']) {
// The feed is being saved, save the categories as well.
if ($edit['category']) {
foreach ($edit['category'] as $cid => $value) {
if ($value) {
db_query('INSERT INTO {aggregator_category_feed} (fid, cid) VALUES (%d, %d)', $edit['fid'], $cid);
}
}
}
}
}
function aggregator_remove($feed) {
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $feed['fid']);
while ($item = db_fetch_object($result)) {
$items[] = "iid = $item->iid";
}
if ($items) {
db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items));
}
db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $feed['fid']);
db_query("UPDATE {aggregator_feed} SET checked = 0, etag = '', modified = 0 WHERE fid = %d", $feed['fid']);
drupal_set_message(t('The news items from %site have been removed.', array('%site' => theme('placeholder', $feed['title']))));
}
/**
* Call-back function used by the XML parser.
*/
function aggregator_element_start($parser, $name, $attributes) {
global $item, $element, $tag, $items, $channel;
switch ($name) {
case 'IMAGE':
case 'TEXTINPUT':
case 'CONTENT':
case 'SUMMARY':
case 'TAGLINE':
case 'SUBTITLE':
case 'LOGO':
case 'INFO':
$element = $name;
break;
case 'ID':
if ($element != 'ITEM') {
$element = $name;
}
case 'LINK':
if ($attributes['REL'] == 'alternate') {
if ($element == 'ITEM') {
$items[$item]['LINK'] = $attributes['HREF'];
}
else {
$channel['LINK'] = $attributes['HREF'];
}
}
break;
case 'ITEM':
$element = $name;
$item += 1;
break;
case 'ENTRY':
$element = 'ITEM';
$item += 1;
break;
}
$tag = $name;
}
/**
* Call-back function used by the XML parser.
*/
function aggregator_element_end($parser, $name) {
global $element;
switch ($name) {
case 'IMAGE':
case 'TEXTINPUT':
case 'ITEM':
case 'ENTRY':
case 'CONTENT':
case 'INFO':
$element = '';
break;
case 'ID':
if ($element == 'ID') {
$element = '';
}
}
}
/**
* Call-back function used by the XML parser.
*/
function aggregator_element_data($parser, $data) {
global $channel, $element, $items, $item, $image, $tag;
switch ($element) {
case 'ITEM':
$items[$item][$tag] .= $data;
break;
case 'IMAGE':
case 'LOGO':
$image[$tag] .= $data;
break;
case 'LINK':
if ($data) {
$items[$item][$tag] .= $data;
}
break;
case 'CONTENT':
$items[$item]['CONTENT'] .= $data;
break;
case 'SUMMARY':
$items[$item]['SUMMARY'] .= $data;
break;
case 'TAGLINE':
case 'SUBTITLE':
$channel['DESCRIPTION'] .= $data;
break;
case 'INFO':
case 'ID':
case 'TEXTINPUT':
// The sub-element is not supported. However, we must recognize
// it or its contents will end up in the item array.
break;
default:
$channel[$tag] .= $data;
}
}
/**
* Checks a news feed for new items.
*/
function aggregator_refresh($feed) {
global $channel, $image;
// Generate conditional GET headers.
$headers = array();
if ($feed['etag']) {
$headers['If-None-Match'] = $feed['etag'];
}
if ($feed['modified']) {
$headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed['modified']) .' GMT';
}
// Request feed.
$result = drupal_http_request($feed['url'], $headers);
// Process HTTP response code.
switch ($result->code) {
case 304:
db_query('UPDATE {aggregator_feed} SET checked = %d WHERE fid = %d', time(), $feed['fid']);
drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => theme('placeholder', $feed['title']))));
break;
case 301:
$feed['url'] = $result->redirect_url;
watchdog('aggregator', t('Updated URL for feed %title to %url.', array('%title' => theme('placeholder', $feed['title']), '%url' => theme('placeholder', $feed['url']))));
break;
case 200:
case 302:
case 307:
// Filter the input data:
if (aggregator_parse_feed($result->data, $feed)) {
if ($result->headers['Last-Modified']) {
$modified = strtotime($result->headers['Last-Modified']);
}
/*
** Prepare the channel data:
*/
foreach ($channel as $key => $value) {
$channel[$key] = trim($value);
}
/*
** Prepare the image data (if any):
*/
foreach ($image as $key => $value) {
$image[$key] = trim($value);
}
if ($image['LINK'] && $image['URL'] && $image['TITLE']) {
// 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
$image = '';
}
else {
$image = NULL;
}
/*
** Update the feed data:
*/
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']);
/*
** Clear the cache:
*/
cache_clear_all();
watchdog('aggregator', t('There is new syndicated content from %site.', array('%site' => theme('placeholder', $feed['title']))));
drupal_set_message(t('There is new syndicated content from %site.', array('%site' => theme('placeholder', $feed['title']))));
}
break;
default:
watchdog('aggregator', t('The RSS-feed from %site seems to be broken, due to "%error".', array('%site' => theme('placeholder', $feed['title']), '%error' => theme('placeholder', $result->code .' '. $result->error))), WATCHDOG_WARNING);
drupal_set_message(t('The RSS-feed from %site seems to be broken, because of error "%error".', array('%site' => theme('placeholder', $feed['title']), '%error' => theme('placeholder', $result->code .' '. $result->error))));
}
}
/**
* Parse the W3C date/time format, a subset of ISO 8601. PHP date parsing
* functions do not handle this format.
* See http://www.w3.org/TR/NOTE-datetime for more information.
* Originally from MagpieRSS (http://magpierss.sourceforge.net/).
*
* @param $date_str A string with a potentially W3C DTF date.
* @return A timestamp if parsed successfully or -1 if not.
*/
function aggregator_parse_w3cdtf($date_str) {
if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) {
list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
// calc epoch for current date assuming GMT
$epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year);
if ($match[10] != 'Z') { // Z is zulu time, aka GMT
list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]);
// zero out the variables
if (!$tz_hour) {
$tz_hour = 0;
}
if (!$tz_min) {
$tz_min = 0;
}
$offset_secs = (($tz_hour * 60) + $tz_min) * 60;
// is timezone ahead of GMT? then subtract offset
if ($tz_mod == '+') {
$offset_secs *= -1;
}
$epoch += $offset_secs;
}
return $epoch;
}
else {
return FALSE;
}
}
function aggregator_parse_feed(&$data, $feed) {
global $items, $image, $channel;
// Unset the global variables before we use them:
unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']);
$items = array();
$image = array();
$channel = array();
// parse the data:
$xml_parser = drupal_xml_parser_create($data);
xml_set_element_handler($xml_parser, 'aggregator_element_start', 'aggregator_element_end');
xml_set_character_data_handler($xml_parser, 'aggregator_element_data');
if (!xml_parse($xml_parser, $data, 1)) {
watchdog('aggregator', t('The RSS-feed from %site seems to be broken, due to an error "%error" on line %line.', array('%site' => theme('placeholder', $feed['title']), '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), WATCHDOG_WARNING);
drupal_set_message(t('The RSS-feed from %site seems to be broken, because of error "%error" on line %line.', array('%site' => theme('placeholder', $feed['title']), '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), 'error');
return 0;
}
xml_parser_free($xml_parser);
/*
** We reverse the array such that we store the first item last,
** and the last item first. In the database, the newest item
** should be at the top.
*/
$items = array_reverse($items);
foreach ($items as $item) {
unset($title, $link, $author, $description);
// Prepare the item:
foreach ($item as $key => $value) {
$item[$key] = trim($value);
}
/*
** Resolve the item's title. If no title is found, we use
** up to 40 characters of the description ending at a word
** boundary but not splitting potential entities.
*/
if ($item['TITLE']) {
$title = $item['TITLE'];
}
else {
$title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['DESCRIPTION'], 40));
}
/*
** Resolve the items link.
*/
if ($item['LINK']) {
$link = $item['LINK'];
}
elseif ($item['GUID'] && (strncmp($item['GUID'], 'http://', 7) == 0)) {
$link = $item['GUID'];
}
else {
$link = $feed['link'];
}
/**
* Atom feeds have a CONTENT and/or SUMMARY tag instead of a DESCRIPTION tag
*/
if ($item['CONTENT:ENCODED']) {
$item['DESCRIPTION'] = $item['CONTENT:ENCODED'];
}
else if ($item['SUMMARY']) {
$item['DESCRIPTION'] = $item['SUMMARY'];
}
/*
** Try to resolve and parse the item's publication date. If no
** date is found, we use the current date instead.
*/
if ($item['PUBDATE']) $date = $item['PUBDATE']; // RSS 2.0
else if ($item['DC:DATE']) $date = $item['DC:DATE']; // Dublin core
else if ($item['DCTERMS:ISSUED']) $date = $item['DCTERMS:ISSUED']; // Dublin core
else if ($item['DCTERMS:CREATED']) $date = $item['DCTERMS:CREATED']; // Dublin core
else if ($item['DCTERMS:MODIFIED']) $date = $item['DCTERMS:MODIFIED']; // Dublin core
else if ($item['ISSUED']) $date = $item['ISSUED']; // Atom XML
else if ($item['CREATED']) $date = $item['CREATED']; // Atom XML
else if ($item['MODIFIED']) $date = $item['MODIFIED']; // Atom XML
else $date = 'now';
$timestamp = strtotime($date); // As of PHP 5.1.0, strtotime returns FALSE on failure instead of -1.
if ($timestamp <= 0) {
$timestamp = aggregator_parse_w3cdtf($date); // Returns FALSE on failure
if (!$timestamp) {
$timestamp = time(); // better than nothing
}
}
/*
** Save this item. Try to avoid duplicate entries as much as
** possible. If we find a duplicate entry, we resolve it and
** pass along it's ID such that we can update it if needed.
*/
if ($link && $link != $feed['link'] && $link != $feed['url']) {
$entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND link = '%s'", $feed['fid'], $link));
}
else {
$entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_item} WHERE fid = %d AND title = '%s'", $feed['fid'], $title));
}
aggregator_save_item(array('iid' => $entry->iid, 'fid' => $feed['fid'], 'timestamp' => $timestamp, 'title' => $title, 'link' => $link, 'author' => $item['AUTHOR'], 'description' => $item['DESCRIPTION']));
}
/*
** Remove all items that are older than flush item timer:
*/
$age = time() - variable_get('aggregator_clear', 9676800);
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
if (db_num_rows($result)) {
$items = array();
while ($item = db_fetch_object($result)) {
$items[] = $item->iid;
}
db_query('DELETE FROM {aggregator_category_item} WHERE iid IN ('. implode(', ', $items) .')');
db_query('DELETE FROM {aggregator_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
}
return 1;
}
function aggregator_save_item($edit) {
if ($edit['iid'] && $edit['title']) {
db_query("UPDATE {aggregator_item} SET title = '%s', link = '%s', author = '%s', description = '%s' WHERE iid = %d", $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['iid']);
}
else if ($edit['iid']) {
db_query('DELETE FROM {aggregator_item} WHERE iid = %d', $edit['iid']);
db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $edit['iid']);
}
else if ($edit['title'] && $edit['link']) {
$edit['iid'] = db_next_id('{aggregator_item}_iid');
db_query("INSERT INTO {aggregator_item} (iid, fid, title, link, author, description, timestamp) VALUES (%d, %d, '%s', '%s', '%s', '%s', %d)", $edit['iid'], $edit['fid'], $edit['title'], $edit['link'], $edit['author'], $edit['description'], $edit['timestamp']);
// file the items in the categories indicated by the feed
$categories = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']);
while ($category = db_fetch_object($categories)) {
db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $category->cid, $edit['iid']);
}
}
}
function aggregator_get_feed($fid) {
return db_fetch_array(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', $fid));
}
function aggregator_get_category($cid) {
return db_fetch_array(db_query('SELECT * FROM {aggregator_category} WHERE cid = %d', $cid));
}
function aggregator_view() {
$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');
$output .= ''. t('Feed overview') .'
';
$header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3'));
$rows = array();
while ($feed = db_fetch_object($result)) {
$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/aggregator/edit/feed/$feed->fid"), l(t('remove items'), "admin/aggregator/remove/$feed->fid"), l(t('update items'), "admin/aggregator/update/$feed->fid"));
}
$output .= theme('table', $header, $rows);
$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');
$output .= ''. t('Category overview') .'
';
$header = array(t('Title'), t('Items'), t('Operations'));
$rows = array();
while ($category = db_fetch_object($result)) {
$rows[] = array(l($category->title, "aggregator/categories/$category->cid"), format_plural($category->items, '1 item', '%count items'), l(t('edit'), "admin/aggregator/edit/category/$category->cid"));
}
$output .= theme('table', $header, $rows);
return $output;
}
/**
* Menu callback; removes all items from a feed, then redirects to the overview page.
*/
function aggregator_admin_remove_feed($feed) {
aggregator_remove(aggregator_get_feed($feed));
drupal_goto('admin/aggregator');
}
/**
* Menu callback; refreshes a feed, then redirects to the overview page.
*/
function aggregator_admin_refresh_feed($feed) {
aggregator_refresh(aggregator_get_feed($feed));
drupal_goto('admin/aggregator');
}
/**
* Menu callback; displays the aggregator administration page.
*/
function aggregator_admin_overview() {
return aggregator_view();
}
/**
* Menu callback; displays the most recent items gathered from any feed.
*/
function aggregator_page_last() {
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));
}
/**
* Menu callback; displays all the items captured from a particular feed.
*/
function aggregator_page_source() {
$feed = db_fetch_object(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', arg(2)));
$info = theme('aggregator_feed', $feed);
return _aggregator_page_list('SELECT * FROM {aggregator_item} WHERE fid = '. $feed->fid .' ORDER BY timestamp DESC, iid DESC', arg(3), $info);
}
/**
* Menu callback; displays all the items aggregated in a particular category.
*/
function aggregator_page_category() {
$category = db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2)));
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));
}
/**
* Prints an aggregator page listing a number of feed items. Various
* menu callbacks use this function to print their feeds.
*/
function _aggregator_page_list($sql, $op, $header = '') {
$categorize = (user_access('administer news feeds') && ($op == 'categorize'));
$output = '';
$form['header'] = array('#value' => $header);
$output .= $form['header']['#value'];
$result = pager_query($sql, 20);
$categories = array();
while ($item = db_fetch_object($result)) {
$form['items'][$item->iid] = array('#value' => theme('aggregator_page_item', $item));
$output .= $form['items'][$item->iid]['#value'];
$form['categories'][$item->iid] = array();
if ($categorize) {
$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);
$selected = array();
while ($category = db_fetch_object($categories_result)) {
if (!$done) {
$categories[$category->cid] = check_plain($category->title);
}
if ($category->iid) {
$selected[] = $category->cid;
}
}
$done = true;
$form['categories'][$item->iid] = array(
'#type' => variable_get('aggregator_category_selector', 'checkboxes'),
'#default_value' => $selected, '#options' => $categories,
'#size' => 10, '#multiple' => true
);
}
}
$output .= '
';
$form['submit'] = array('#type' => 'submit', '#value' => t('Save categories'));
$form['pager'] = array('#value' => theme('pager', NULL, 20, 0));
$output .= $form['pager']['#value'];
// arg(1) is undefined if we are at the top aggregator URL
// is there a better way to do this?
if (!arg(1)) {
$form['feed_icon'] = array('#value' => theme('feed_icon', url('aggregator/rss')));
}
elseif (arg(1) == 'categories' && arg(2) && !arg(3)) {
$form['feed_icon'] = array('#value' => theme('feed_icon', url('aggregator/rss/' . arg(2))));
}
$output .= $form['feed_icon']['#value'];
return ($categorize) ? drupal_get_form('aggregator_page_list', $form) : $output;
}
function theme_aggregator_page_list($form) {
$output = '';
$output .= form_render($form['header']);
$rows = array();
if ($form['items']) {
foreach (element_children($form['items']) as $key) {
if (is_array($form['items'][$key])) {
$rows[] = array(form_render($form['items'][$key]), array('data' => form_render($form['categories'][$key]), 'class' => 'categorize-item'));
}
}
}
$output .= theme('table', array('', t('Categorize')), $rows);
$output .= form_render($form['submit']);
$output .= '
';
$output .= form_render($form);
return $output;
}
function aggregator_page_list_validate($form_id, &$form) {
if (!user_access('administer news feeds')) {
form_error($form, t('You are not allowed to categorize this feed item.'));
}
}
function aggregator_page_list_submit($form_id, $form_values) {
foreach ($form_values as $iid => $selection) {
db_query('DELETE FROM {aggregator_category_item} WHERE iid = %d', $iid);
foreach ($selection as $cid) {
if ($cid) {
db_query('INSERT INTO {aggregator_category_item} (cid, iid) VALUES (%d, %d)', $cid, $iid);
}
}
}
drupal_set_message(t('The categories have been saved.'));
}
/**
* Menu callback; displays all the feeds used by the aggregator.
*/
function aggregator_page_sources() {
$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');
$output = "\n";
while ($feed = db_fetch_object($result)) {
$output .= '
'. check_plain($feed->title) ."
\n";
// Most recent items:
$list = array();
if (variable_get('aggregator_summary_items', 3)) {
$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));
while ($item = db_fetch_object($items)) {
$list[] = theme('aggregator_summary_item', $item);
}
}
$output .= theme('item_list', $list);
$output .= '
'. theme('links', array(l(t('more'), 'aggregator/sources/'. $feed->fid))) ."
\n";
}
$output .= theme('xml_icon', url('aggregator/opml'));
$output .= '
';
return $output;
}
/**
* Menu callback; generate an RSS 0.92 feed of aggregator items or categories.
*/
function aggregator_page_rss() {
global $base_url;
// arg(2) is the passed cid, only select for that category
$result = NULL;
if (arg(2)) {
$category = db_fetch_object(db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = %d', arg(2)));
$url = '/categories/' . $category->cid;
$title = ' ' . t('in category') . ' ' . $category->title;
$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';
$result = db_query_range($sql, $category->cid, 0, variable_get('feed_default_items', 10));
}
// or, get the default aggregator items
else {
$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';
$result = db_query_range($sql, 0, variable_get('feed_default_items', 10));
}
while ($item = db_fetch_object($result)) {
switch (variable_get('feed_item_length', 'teaser')) {
case 'teaser':
$teaser = node_teaser($item->description);
if ($teaser != $item->description) {
$teaser .= ''. t('read more') ."
\n";
}
$item->description = $teaser;
break;
case 'title':
$item->description = '';
break;
}
$items .= format_rss_item($item->ftitle . ': ' . $item->title, $item->link, $item->description, array('pubDate' => date('r', $item->timestamp)));
}
$output .= "\n";
$output .= "\n";
$output .= format_rss_channel(variable_get('site_name', t('Drupal')) . ' ' . t('aggregator'), $base_url . '/' . url('aggregator' . $url), variable_get('site_name', t('Drupal')) . ' - ' . t('aggregated feeds') . $title, $items, 'en');
$output .= "\n";
drupal_set_header('Content-Type: text/xml; charset=utf-8');
print $output;
}
/**
* Menu callback; generates an OPML representation of all feeds.
*/
function aggregator_page_opml($cid = NULL) {
if ($cid) {
$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);
}
else {
$result = db_query('SELECT * FROM {aggregator_feed} ORDER BY title');
}
$output = "\n";
$output .= "\n";
$output .= "\n";
$output .= ''. check_plain(variable_get('site_name', 'Drupal')) ."\n";
$output .= ''. gmdate('r') ."\n";
$output .= "\n";
$output .= "\n";
while ($feed = db_fetch_object($result)) {
$output .= '\n";
}
$output .= "\n";
$output .= "\n";
drupal_set_header('Content-Type: text/xml; charset=utf-8');
print $output;
}
/**
* Menu callback; displays all the categories used by the aggregator.
*/
function aggregator_page_categories() {
$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');
$output = "\n";
while ($category = db_fetch_object($result)) {
$output .= '
'. check_plain($category->title) ."
\n";
if (variable_get('aggregator_summary_items', 3)) {
$list = array();
$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));
while ($item = db_fetch_object($items)) {
$list[] = theme('aggregator_summary_item', $item);
}
$output .= theme('item_list', $list);
}
$output .= '
'. theme('links', array(l(t('more'), 'aggregator/categories/'. $category->cid))) ."
\n";
}
$output .= '
';
return $output;
}
/**
* Format a news feed.
*
* @ingroup themeable
*/
function theme_aggregator_feed($feed) {
$output = '';
$output .= theme('feed_icon', $feed->url) ."\n";
$output .= $feed->image;
$output .= '
'. aggregator_filter_xss($feed->description) ."
\n";
$output .= '
'. t('URL:') .' '. l($feed->link, $feed->link, array(), NULL, NULL, TRUE) ."
\n";
if ($feed->checked) {
$updated = t('%time ago', array('%time' => format_interval(time() - $feed->checked)));
}
else {
$updated = t('never');
}
if (user_access('administer news feeds')) {
$updated = l($updated, 'admin/aggregator');
}
$output .= '
'. t('Updated:') . " $updated
";
$output .= "
\n";
return $output;
}
/**
* Format an individual feed item for display in the block.
*
* @ingroup themeable
*/
function theme_aggregator_block_item($item, $feed = 0) {
global $user;
if ($user->uid && module_exist('blog') && user_access('edit own blog')) {
if ($image = theme('image', 'misc/blog.png', t('blog it'), t('blog it'))) {
$output .= ''. 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) .'
';
}
}
// Display the external link to the item.
$output .= ''. check_plain($item->title) ."\n";
return $output;
}
/**
* Return a themed item heading for summary pages located at "aggregator/sources"
* and "aggregator/categories".
*
* @param $item The item object from the aggregator module.
* @return A string containing the output.
*
* @ingroup themeable
*/
function theme_aggregator_summary_item($item) {
$output = ''. check_plain($item->title) .' '. t('%age old', array('%age' => format_interval(time() - $item->timestamp))) .'';
if ($item->feed_link) {
$output .= ', '. check_plain($item->feed_title) .'';
}
return $output ."\n";
}
/**
* Format an individual feed item for display on the aggregator page.
*
* @ingroup themeable
*/
function theme_aggregator_page_item($item) {
$source = '';
if ($item->ftitle && $item->fid) {
$source = l($item->ftitle, "aggregator/sources/$item->fid", array('class' => 'feed-item-source')) . ' -';
}
if (date('Ymd', $item->timestamp) == date('Ymd')) {
$source_date = t('%ago ago', array('%ago' => format_interval(time() - $item->timestamp)));
}
else {
$source_date = format_date($item->timestamp, 'custom', variable_get('date_format_medium', 'D, Y-m-d H:i'));
}
$output .= "\n";
$output .= '
\n";
$output .= "
$source $source_date
\n";
if ($item->description) {
$output .= '
'. aggregator_filter_xss($item->description) ."
\n";
}
$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);
$categories = array();
while ($category = db_fetch_object($result)) {
$categories[] = l($category->title, 'aggregator/categories/'. $category->cid);
}
if ($categories) {
$output .= '
'. t('Categories') .': '. implode(', ', $categories) ."
\n";
}
$output .= "
\n";
return $output;
}
/**
* Safely render HTML content, as allowed.
*/
function aggregator_filter_xss($value) {
return filter_xss($value, preg_split('/\s+|<|>/', variable_get("aggregator_allowed_html_tags", '
-
-
-
'), -1, PREG_SPLIT_NO_EMPTY));
}
/**
* Helper function for drupal_map_assoc.
*/
function _aggregator_items($count) {
return format_plural($count, '1 item', '%count items');
}