/[drupal]/contributions/modules/aggregator_summary/aggregator_summary.module
ViewVC logotype

Contents of /contributions/modules/aggregator_summary/aggregator_summary.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.3 - (show annotations) (download) (as text)
Mon Sep 3 14:55:34 2007 UTC (2 years, 2 months ago) by evakoss
Branch: MAIN
CVS Tags: HEAD
Changes since 1.2: +1 -1 lines
File MIME type: text/x-php
*** empty log message ***
1 <?php
2 // $Id: aggregator_summary.module,v 1.1 2007/07/16 11:11:43 evakoss 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_summary_help($section) {
13 switch ($section) {
14 case 'admin/help#aggregator_summary':
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_summary/sources'), '@admin-block' => url('admin/build/block'), '@aggregator-opml' => url('aggregator_summary/opml'))) .'</p>';
17
18 return $output;
19 case 'admin/content/aggregator_summary':
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_summary/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_summary/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_summary_menu($may_cache)
32 {
33 $items = array();
34 $edit = user_access('administer news feeds');
35 $view = user_access('access news feeds');
36 if ($may_cache)
37 {
38 $items[] = array('path' => 'admin/content/aggregator_summary',
39 'title' => t('Aggregator Summary'),
40 'description' => t("Configure which content your site aggregates from other sites, how often it polls them, and how they're categorized."),
41 'callback' => 'aggregator_summary_admin_overview',
42 'access' => $edit);
43 $items[] = array('path' => 'admin/content/aggregator_summary/add/section',
44 'title' => t('Add Section'),
45 'callback' => 'drupal_get_form',
46 'callback arguments' => array('aggregator_summary_form_section'),
47 'access' => $edit,
48 'weight' => -10,
49 'type' => MENU_LOCAL_TASK);
50 $items[] = array('path' => 'admin/content/aggregator_summary/add/category',
51 'title' => t('Add Category'),
52 'callback' => 'drupal_get_form',
53 'callback arguments' => array('aggregator_summary_form_category'),
54 'access' => $edit,
55 'weight' => -9,
56 'type' => MENU_LOCAL_TASK);
57 $items[] = array('path' => 'admin/content/aggregator_summary/add/feed',
58 'title' => t('Add Feed'),
59 'callback' => 'drupal_get_form',
60 'callback arguments' => array('aggregator_summary_form_feed'),
61 'access' => $edit,
62 'weight' => -8,
63 'type' => MENU_LOCAL_TASK);
64 $items[] = array('path' => 'admin/content/aggregator_summary/general',
65 'title' => t('General Setting'),
66 'callback' => 'drupal_get_form',
67 'callback arguments' => array('aggregator_summary_form_general'),
68 'access' => $edit,
69 'weight' => -7,
70 'type' => MENU_LOCAL_TASK);
71 $items[] = array('path' => 'admin/content/aggregator_summary/remove',
72 'title' => t('Remove items'),
73 'callback' => 'aggregator_summary_admin_remove_feed',
74 'access' => $edit,
75 'type' => MENU_CALLBACK);
76 $items[] = array('path' => 'admin/content/aggregator_summary/update',
77 'title' => t('Update items'),
78 'callback' => 'aggregator_summary_admin_refresh_feed',
79 'access' => $edit,
80 'type' => MENU_CALLBACK);
81 $items[] = array('path' => 'admin/content/aggregator_summary/list',
82 'title' => t('List'),
83 'type' => MENU_DEFAULT_LOCAL_TASK,
84 'weight' => -11);
85 $items[] = array('path' => 'admin/content/aggregator_summary/settings',
86 'title' => t('Settings'),
87 'callback' => 'drupal_get_form',
88 'callback arguments' => array('aggregator_summary_admin_settings'),
89 'type' => MENU_LOCAL_TASK,
90 'weight' => 10,
91 'access' => $edit);
92 $items[] = array('path' => 'aggregator_summary',
93 'title' => t('Aggregator Summary'),
94 'callback' => 'aggregator_summary_page_last',
95 'access' => $view,
96 'weight' => 5);
97 $items[] = array('path' => 'aggregator_summary/rss',
98 'title' => t('RSS feed'),
99 'callback' => 'aggregator_summary_page_rss',
100 'access' => $view,
101 'type' => MENU_CALLBACK);
102 $items[] = array('path' => 'aggregator_summary/opml',
103 'title' => t('OPML feed'),
104 'callback' => 'aggregator_summary_page_opml',
105 'access' => $view,
106 'type' => MENU_CALLBACK);
107 } else {
108 // Add the CSS for this module
109 // We put this in !$may_cache so it's only added once per request
110 drupal_add_css(drupal_get_path('module', 'aggregator_summary') .'/aggregator_summary.css');
111 if (arg(0) == 'aggregator_summary' && is_numeric(arg(2)))
112 {
113 if (arg(1) == 'sources')
114 {
115 $feed = aggregator_summary_get_feed(arg(2));
116 if ($feed)
117 {
118 $items[] = array('path' => 'aggregator_summary/sources/'. $feed['fid'],
119 'title' => $feed['title'],
120 'callback' => 'aggregator_summary_page_source',
121 'access' => $view,
122 'type' => MENU_CALLBACK);
123 $items[] = array('path' => 'aggregator_summary/sources/'. $feed['fid'] .'/view',
124 'title' => t('View'),
125 'type' => MENU_DEFAULT_LOCAL_TASK,
126 'weight' => -10);
127 $items[] = array('path' => 'aggregator_summary/sources/'. $feed['fid'] .'/configure',
128 'title' => t('Configure'),
129 'callback' => 'drupal_get_form',
130 'callback arguments' => array('aggregator_summary_form_feed', $feed),
131 'access' => $edit,
132 'type' => MENU_LOCAL_TASK,
133 'weight' => 1);
134 }
135 } else if(arg(1) == 'categories' && is_numeric(arg(2))) {
136 $category = aggregator_summary_get_category(arg(2));
137 $items[] = array('path' => 'aggregator_summary/categories/'. arg(2),
138 'title' => $category->title,
139 'callback' => 'aggregator_summary_page_category',
140 'access' => $view,
141 'type' => MENU_LOCAL_TASK);
142 }
143 } else if (arg(2) == 'aggregator_summary' && is_numeric(arg(5))) {
144 if (arg(4) == 'feed')
145 {
146 $feed = aggregator_summary_get_feed(arg(5));
147 if ($feed)
148 {
149 $items[] = array('path' => 'admin/content/aggregator_summary/edit/feed/'. $feed['fid'],
150 'title' => t('Edit feed'),
151 'callback' => 'drupal_get_form',
152 'callback arguments' => array('aggregator_summary_form_feed', $feed),
153 'access' => $edit,
154 'type' => MENU_CALLBACK);
155 }
156 } else {
157 if(arg(4) == 'category' && is_numeric(arg(5)))
158 {
159 $category = aggregator_summary_get_category(arg(5));
160 if ($category)
161 {
162 $items[] = array('path' => 'admin/content/aggregator_summary/edit/category/'. $category['cid'],
163 'title' => t('Edit category'),
164 'callback' => 'drupal_get_form',
165 'callback arguments' => array('aggregator_summary_form_category', $category),
166 'access' => $edit,
167 'type' => MENU_CALLBACK);
168 }
169 } else {
170 if(arg(4) == 'section' && is_numeric(arg(5)))
171 {
172 $section = aggregator_summary_get_section(arg(5));
173 if ($section)
174 {
175 $items[] = array('path' => 'admin/content/aggregator_summary/edit/section/'. $section['sid'],
176 'title' => t('edit section'),
177 'callback' => 'drupal_get_form',
178 'callback arguments' => array(aggregator_summary_form_section,$section),
179 'access' => $edit,
180 'type' => MENU_CALLBACK);
181 }
182 } else {
183 if(arg(4) == 'items')
184 {
185 $items[] = array('path' => 'admin/content/aggregator_summary/edit/items/'. arg(5),
186 'title' => t('edit items'),
187 'callback' => 'drupal_get_form',
188 'callback arguments' => array(aggregator_summary_page_items),
189 'access' => $edit,
190 'type' => MENU_CALLBACK);
191 } else {
192 if(arg(3)=='item' && is_numeric(arg(5)))
193 {
194 $item = aggregator_summary_get_item(arg(5));
195 if ($item)
196 {
197 $items[] = array('path' => 'admin/content/aggregator_summary/item/edit/'. $item['iid'],
198 'title' => t('Edit Item'),
199 'callback' => 'drupal_get_form',
200 'callback arguments' => array('aggregator_summary_form_item', $item),
201 'access' => $edit,
202 'type' => MENU_CALLBACK);
203 }
204 }
205 }
206 }
207 }
208 }
209 }
210 }
211 return $items;
212 }
213 function aggregator_summary_admin_settings()
214 {
215 $items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10), '_aggregator_summary_items1');
216 $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
217 $form['aggregator_summary_allowed_html_tags'] = array(
218 '#type' => 'textfield', '#title' => t('Allowed HTML tags'), '#size' => 80, '#maxlength' => 255,
219 '#default_value' => variable_get('aggregator_summary_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'),
220 '#description' => t('The list of tags which are allowed in feeds, i.e., which will not be removed by Drupal.')
221 );
222 $form['aggregator_summary_items1'] = array(
223 '#type' => 'select', '#title' => t('Items shown in categories pages') ,
224 '#default_value' => variable_get('aggregator_summary_summary_items', 3), '#options' => $items,
225 '#description' => t('The number of items which will be shown with each feed or category in the feed and category summary pages.')
226 );
227 $form['aggregator_summary_clear'] = array(
228 '#type' => 'select', '#title' => t('Discard news items older than'),
229 '#default_value' => variable_get('aggregator_summary_clear', 9676800), '#options' => $period,
230 '#description' => t('Older news items will be automatically discarded. Requires crontab.')
231 );
232 return system_settings_form($form);
233 }
234 /**
235 * Implementation of hook_perm().
236 */
237 function aggregator_summary_perm()
238 {
239 return array('administer news feeds', 'access news feeds');
240 }
241 /**
242 * Implementation of hook_cron().
243 *
244 * Checks news feeds for updates once their refresh interval has elapsed.
245 */
246 function aggregator_summary_cron()
247 {
248 $result = db_query('SELECT * FROM {aggregator_summary_feed} WHERE checked + refresh < %d', time());
249 while ($feed = db_fetch_array($result))
250 {
251 aggregator_summary_refresh($feed);
252 }
253 }
254 /**
255 * Implementation of hook_block().
256 *
257 * Generates blocks for the latest news items in each category and feed.
258 */
259 function aggregator_summary_block($op = 'list', $delta = 0, $edit = array())
260 {
261 if (user_access('access news feeds'))
262 {
263 if ($op == 'list')
264 {
265 $result = db_query('SELECT cid, title FROM {aggregator_summary_category} ORDER BY title');
266 while ($category = db_fetch_object($result))
267 {
268 $block['category-'. $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title));
269 }
270 $result = db_query('SELECT fid, title FROM {aggregator_summary_feed} ORDER BY fid');
271 while ($feed = db_fetch_object($result)) {
272 $block['feed-'. $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title));
273 }
274 }
275 else if ($op == 'configure')
276 {
277 list($type, $id) = explode('-', $delta);
278 if ($type == 'category')
279 {
280 $value = db_result(db_query('SELECT block FROM {aggregator_summary_category} WHERE cid = %d', $id));
281 }
282 else
283 {
284 $value = db_result(db_query('SELECT block FROM {aggregator_summary_feed} WHERE fid = %d', $id));
285 }
286 $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)));
287 return $form;
288 }
289 else if ($op == 'save')
290 {
291 list($type, $id) = explode('-', $delta);
292 if ($type == 'category')
293 {
294 $value = db_query('UPDATE {aggregator_summary_category} SET block = %d WHERE cid = %d', $edit['block'], $id);
295 }
296 else
297 {
298 $value = db_query('UPDATE {aggregator_summary_feed} SET block = %d WHERE fid = %d', $edit['block'], $id);
299 }
300 }
301 else if ($op == 'view')
302 {
303 list($type, $id) = explode('-', $delta);
304 switch ($type)
305 {
306 case 'feed':
307 if ($feed = db_fetch_object(db_query('SELECT fid, title, block FROM {aggregator_summary_feed} WHERE fid = %d', $id)))
308 {
309 $block['subject'] = check_plain($feed->title);
310 $result = db_query_range('SELECT * FROM {aggregator_summary_item} WHERE fid = %d ORDER BY timestamp DESC, iid DESC', $feed->fid, 0, $feed->block);
311 $read_more = '<div class="more-link">'. l(t('more'), 'aggregator_summary/sources/'. $feed->fid, array('title' => t("View this feed's recent news."))) .'</div>';
312 }
313 break;
314 case 'category':
315 if ($category = db_fetch_object(db_query('SELECT cid, title, block FROM {aggregator_summary_category} WHERE cid = %d', $id)))
316 {
317 $block['subject'] = check_plain($category->title);
318 $result = db_query_range('SELECT i.* FROM {aggregator_summary_category_item} ci LEFT JOIN {aggregator_summary_item} i ON ci.iid = i.iid WHERE ci.cid = %d ORDER BY i.timestamp DESC, i.iid DESC', $category->cid, 0, $category->block);
319 $read_more = '<div class="more-link">'. l(t('more'), 'aggregator_summary/categories/'. $category->cid, array('title' => t("View this category's recent news."))) .'</div>';
320 }
321 break;
322 }
323 $items = array();
324 while ($item = db_fetch_object($result))
325 {
326 $items[] = theme('aggregator_summary_block_item', $item);
327 }
328 // Only display the block if there are items to show.
329 if (count($items) > 0)
330 {
331 $block['content'] = theme('item_list', $items) . $read_more;
332 }
333 }
334 return $block;
335 }
336 }
337 /**
338 * Generate a form to add/edit/delete aggregator categories.
339 */
340 function aggregator_summary_form_category($edit = array())
341 {
342 $form['title'] = array('#type' => 'textfield',
343 '#title' => t('Title'),
344 '#default_value' => $edit['title'],
345 '#maxlength' => 64,
346 '#required' => TRUE,
347 );
348 $form['item_perpage'] = array('#type' => 'textfield',
349 '#title' => t('No of feeds per page'),
350 '#default_value' => $edit['item_perpage'],
351 '#maxlength' => 64,
352 '#required' => TRUE,
353 );
354 $form['description'] = array('#type' => 'textarea',
355 '#title' => t('Description'),
356 '#default_value' => $edit['description'],
357 );
358 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
359 if ($edit['cid'])
360 {
361 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
362 $form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']);
363 }
364 return $form;
365 }
366
367 /**
368 * Validate aggregator_summary_form_feed form submissions.
369 */
370 function aggregator_summary_form_category_validate($form_id, $form_values)
371 {
372 if ($form_values['op'] == t('Submit'))
373 {
374 // Check for duplicate titles
375 if (isset($form_values['cid']))
376 {
377 $category = db_fetch_object(db_query("SELECT cid FROM {aggregator_summary_category} WHERE title = '%s' AND cid != %d", $form_values['title'], $form_values['cid']));
378 }
379 else
380 {
381 $category = db_fetch_object(db_query("SELECT cid FROM {aggregator_summary_category} WHERE title = '%s'", $form_values['title']));
382 }
383 if ($category)
384 {
385 form_set_error('title', t('A category named %category already exists. Please enter a unique title.', array('%category' => $form_values['title'])));
386 }
387 }
388 }
389 /**
390 * Process aggregator_summary_form_category form submissions.
391 * @todo Add delete confirmation dialog.
392 */
393 function aggregator_summary_form_category_submit($form_id, $form_values) {
394 if ($_POST['op'] == t('Delete')) {
395 $title = $form_values['title'];
396 // Unset the title:
397 unset($form_values['title']);
398 }
399 aggregator_summary_save_category($form_values);
400 menu_rebuild();
401 if (isset($form_values['cid'])) {
402 if (isset($form_values['title'])) {
403 drupal_set_message(t('The category %category has been updated.', array('%category' => $form_values['title'])));
404 if (arg(0) == 'admin') {
405 return 'admin/content/aggregator_summary/';
406 }
407 else {
408 return 'aggregator_summary/categories/'. $form_values['cid'];
409 }
410 }
411 else {
412 watchdog('aggregator', t('Category %category deleted.', array('%category' => theme('placeholder', $title))));
413 drupal_set_message(t('The category %category has been deleted.', array('%category' => $title)));
414 if (arg(0) == 'admin') {
415 return 'admin/content/aggregator_summary/';
416 }
417 else {
418 return 'aggregator_summary/categories/';
419 }
420 }
421 }
422 else {
423 watchdog('aggregator', t('Category %category added.', array('%category' => theme('placeholder', $form_values['title']))), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator_summary'));
424 drupal_set_message(t('The category %category has been added.', array('%category' => $form_values['title'])));
425 }}
426
427 /**
428 * Add/edit/delete aggregator categories.
429 */
430 function aggregator_summary_save_category($edit) {
431 if ($edit['cid'] && $edit['title']) {
432 db_query("UPDATE {aggregator_summary_category} SET title = '%s', item_perpage = %d, description = '%s' WHERE cid = %d", $edit['title'], $edit['item_perpage'],$edit['description'], $edit['cid']);
433 }
434 else if ($edit['cid']) {
435 db_query('DELETE FROM {aggregator_summary_category} WHERE cid = %d', $edit['cid']);
436 }
437 else if ($edit['title']) {
438 // A single unique id for bundles and feeds, to use in blocks
439 $next_id = db_next_id('{aggregator_summary_category}_cid');
440 db_query("INSERT INTO {aggregator_summary_category} (cid, title,item_perpage, description, block) VALUES (%d, '%s', %d , '%s', 5)", $next_id, $edit['title'],$edit['item_perpage'], $edit['description']);
441 }
442 }
443
444 /**
445 * Generate a form to add/edit feed sources.
446 */
447 function aggregator_summary_form_feed($edit = array()) {
448 $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
449
450 if ($edit['refresh'] == '') {
451 $edit['refresh'] = 3600;
452 }
453
454 $form['title'] = array('#type' => 'textfield',
455 '#title' => t('Title'),
456 '#default_value' => $edit['title'],
457 '#maxlength' => 255,
458 '#description' => t('The name of the feed; typically the name of the web site you syndicate content from.'),
459 '#required' => TRUE,
460 );
461 $form['url'] = array('#type' => 'textfield',
462 '#title' => t('URL'),
463 '#default_value' => $edit['url'],
464 '#maxlength' => 255,
465 '#description' => t('The fully-qualified URL of the feed.'),
466 '#required' => TRUE,
467 );
468 $form['refresh'] = array('#type' => 'select',
469 '#title' => t('Update interval'),
470 '#default_value' => $edit['refresh'],
471 '#options' => $period,
472 '#description' => t('The refresh interval indicating how often you want to update this feed. Requires crontab.'),
473 );
474
475 // Handling of categories:
476 $options = array();
477 $values = array();
478 $categories = db_query('SELECT c.cid, c.title, f.fid FROM {aggregator_summary_category} c LEFT JOIN {aggregator_summary_category_feed} f ON c.cid = f.cid AND f.fid = %d ORDER BY title', $edit['fid']);
479 while ($category = db_fetch_object($categories)) {
480 $options[$category->cid] = check_plain($category->title);
481 if ($category->fid) $values[] = $category->cid;
482 }
483 if ($options) {
484 $form['category'] = array('#type' => 'checkboxes',
485 '#title' => t('Categorize news items'),
486 '#default_value' => $values,
487 '#options' => $options,
488 '#description' => t('New items in this feed will be automatically filed in the checked categories as they are received.'),
489 );
490 }
491 $form['submit'] = array('#type' => 'submit', '#value' => t('Submit'));
492
493 if ($edit['fid']) {
494 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
495 $form['fid'] = array('#type' => 'hidden', '#value' => $edit['fid']);
496 }
497
498 return $form;
499 }
500
501 /**
502 * Validate aggregator_summary_form_feed form submissions.
503 */
504 function aggregator_summary_form_feed_validate($form_id, $form_values) {
505 if ($form_values['op'] == t('Submit')) {
506 // Check for duplicate titles
507 if (isset($form_values['fid'])) {
508 $result = db_query("SELECT title, url FROM {aggregator_summary_feed} WHERE (title = '%s' OR url='%s') AND fid != %d", $form_values['title'], $form_values['url'], $form_values['fid']);
509 }
510 else {
511 $result = db_query("SELECT title, url FROM {aggregator_summary_feed} WHERE title = '%s' OR url='%s'", $form_values['title'], $form_values['url']);
512 }
513 while ($feed = db_fetch_object($result)) {
514 if (strcasecmp($feed->title, $form_values['title']) == 0) {
515 form_set_error('title', t('A feed named %feed already exists. Please enter a unique title.', array('%feed' => $form_values['title'])));
516 }
517 if (strcasecmp($feed->url, $form_values['url']) == 0) {
518 form_set_error('url', t('A feed with this URL %url already exists. Please enter a unique URL.', array('%url' => $form_values['url'])));
519 }
520 }
521 }
522 }
523
524 /**
525 * Process aggregator_summary_form_feed form submissions.
526 * @todo Add delete confirmation dialog.
527 */
528 function aggregator_summary_form_feed_submit($form_id, $form_values) {
529 if ($form_values['op'] == t('Delete')) {
530 $title = $form_values['title'];
531 // Unset the title:
532 unset($form_values['title']);
533 }
534 aggregator_summary_save_feed($form_values);
535 menu_rebuild();
536 if (isset($form_values['fid'])) {
537 if (isset($form_values['title'])) {
538 drupal_set_message(t('The feed %feed has been updated.', array('%feed' => $form_values['title'])));
539 if (arg(0) == 'admin') {
540 return 'admin/content/aggregator_summary/';
541 }
542 else {
543 return 'aggregator_summary/sources/'. $form_values['fid'];
544 }
545 }
546 else {
547 watchdog('aggregator', t('Feed %feed deleted.', array('%feed' => $title)));
548 drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => $title)));
549 if (arg(0) == 'admin') {
550 return 'admin/content/aggregator_summary/';
551 }
552 else {
553 return 'aggregator_summary/sources/';
554 }
555 }
556 }
557 else {
558 watchdog('aggregator', t('Feed %feed added.', array('%feed' => $form_values['title'])), WATCHDOG_NOTICE, l(t('view'), 'admin/content/aggregator_summary'));
559 drupal_set_message(t('The feed %feed has been added.', array('%feed' => $form_values['title'])));
560 }
561 }
562
563 /**
564 * Add/edit/delete an aggregator feed.
565 */
566 function aggregator_summary_save_feed($edit) {
567 if ($edit['fid']) {
568 // An existing feed is being modified, delete the category listings.
569 db_query('DELETE FROM {aggregator_summary_category_feed} WHERE fid = %d', $edit['fid']);
570 }
571 if ($edit['fid'] && $edit['title']) {
572 db_query("UPDATE {aggregator_summary_feed} SET title = '%s', url = '%s', refresh = %d WHERE fid = %d", $edit['title'], $edit['url'], $edit['refresh'], $edit['fid']);
573 }
574 else if ($edit['fid']) {
575 $result = db_query('SELECT iid FROM {aggregator_summary_item} WHERE fid = %d', $edit['fid']);
576 while ($item = db_fetch_object($result)) {
577 $items[] = "iid = $item->iid";
578 }
579 if ($items) {
580 db_query('DELETE FROM {aggregator_summary_category_item} WHERE '. implode(' OR ', $items));
581 }
582 db_query('DELETE FROM {aggregator_summary_feed} WHERE fid = %d', $edit['fid']);
583 db_query('DELETE FROM {aggregator_summary_item} WHERE fid = %d', $edit['fid']);
584 }
585 else if ($edit['title']) {
586 // A single unique id for bundles and feeds, to use in blocks.
587 $edit['fid'] = db_next_id('{aggregator_summary_feed}_fid');
588 db_query("INSERT INTO {aggregator_summary_feed} (fid, title, url, refresh, block) VALUES (%d, '%s', '%s', %d, 5)", $edit['fid'], $edit['title'], $edit['url'], $edit['refresh']);
589 }
590 if ($edit['title']) {
591 // The feed is being saved, save the categories as well.
592 if ($edit['category']) {
593 foreach ($edit['category'] as $cid => $value) {
594 if ($value) {
595 db_query('INSERT INTO {aggregator_summary_category_feed} (fid, cid) VALUES (%d, %d)', $edit['fid'], $cid);
596 }
597 }
598 }
599 }
600 }
601
602 function aggregator_summary_remove($feed) {
603 $result = db_query('SELECT iid FROM {aggregator_summary_item} WHERE fid = %d', $feed['fid']);
604 while ($item = db_fetch_object($result)) {
605 $items[] = "iid = $item->iid";
606 }
607 if ($items) {
608 db_query('DELETE FROM {aggregator_summary_category_item} WHERE '. implode(' OR ', $items));
609 }
610 db_query('DELETE FROM {aggregator_summary_item} WHERE fid = %d', $feed['fid']);
611 db_query("UPDATE {aggregator_summary_feed} SET checked = 0, etag = '', modified = 0 WHERE fid = %d", $feed['fid']);
612 drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed['title'])));
613 }
614
615 /**
616 * Call-back function used by the XML parser.
617 */
618 function aggregator_summary_element_start($parser, $name, $attributes) {
619 global $item, $element, $tag, $items, $channel;
620
621 switch ($name) {
622 case 'IMAGE':
623 case 'TEXTINPUT':
624 case 'CONTENT':
625 case 'SUMMARY':
626 case 'TAGLINE':
627 case 'SUBTITLE':
628 case 'LOGO':
629 case 'INFO':
630 $element = $name;
631 break;
632 case 'ID':
633 if ($element != 'ITEM') {
634 $element = $name;
635 }
636 case 'LINK':
637 if ($attributes['REL'] == 'alternate') {
638 if ($element == 'ITEM') {
639 $items[$item]['LINK'] = $attributes['HREF'];
640 }
641 else {
642 $channel['LINK'] = $attributes['HREF'];
643 }
644 }
645 break;
646 case 'ITEM':
647 $element = $name;
648 $item += 1;
649 break;
650 case 'ENTRY':
651 $element = 'ITEM';
652 $item += 1;
653 break;
654 }
655
656 $tag = $name;
657 }
658
659 /**
660 * Call-back function used by the XML parser.
661 */
662 function aggregator_summary_element_end($parser, $name) {
663 global $element;
664
665 switch ($name) {
666 case 'IMAGE':
667 case 'TEXTINPUT':
668 case 'ITEM':
669 case 'ENTRY':
670 case 'CONTENT':
671 case 'INFO':
672 $element = '';
673 break;
674 case 'ID':
675 if ($element == 'ID') {
676 $element = '';
677 }
678 }
679 }
680
681 /**
682 * Call-back function used by the XML parser.
683 */
684 function aggregator_summary_element_data($parser, $data) {
685 global $channel, $element, $items, $item, $image, $tag;
686 switch ($element) {
687 case 'ITEM':
688 $items[$item][$tag] .= $data;
689 break;
690 case 'IMAGE':
691 case 'LOGO':
692 $image[$tag] .= $data;
693 break;
694 case 'LINK':
695 if ($data) {
696 $items[$item][$tag] .= $data;
697 }
698 break;
699 case 'CONTENT':
700 $items[$item]['CONTENT'] .= $data;
701 break;
702 case 'SUMMARY':
703 $items[$item]['SUMMARY'] .= $data;
704 break;
705 case 'TAGLINE':
706 case 'SUBTITLE':
707 $channel['DESCRIPTION'] .= $data;
708 break;
709 case 'INFO':
710 case 'ID':
711 case 'TEXTINPUT':
712 // The sub-element is not supported. However, we must recognize
713 // it or its contents will end up in the item array.
714 break;
715 default:
716 $channel[$tag] .= $data;
717 }
718 }
719
720 /**
721 * Checks a news feed for new items.
722 */
723 function aggregator_summary_refresh($feed) {
724 global $channel, $image;
725
726 // Generate conditional GET headers.
727 $headers = array();
728 if ($feed['etag']) {
729 $headers['If-None-Match'] = $feed['etag'];
730 }
731 if ($feed['modified']) {
732 $headers['If-Modified-Since'] = gmdate('D, d M Y H:i:s', $feed['modified']) .' GMT';
733 }
734
735 // Request feed.
736 $result = drupal_http_request($feed['url'], $headers);
737
738 // Process HTTP response code.
739 switch ($result->code) {
740 case 304:
741 db_query('UPDATE {aggregator_summary_feed} SET checked = %d WHERE fid = %d', time(), $feed['fid']);
742 drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed['title'])));
743 break;
744 case 301:
745 $feed['url'] = $result->redirect_url;
746 watchdog('aggregator', t('Updated URL for feed %title to %url.', array('%title' => $feed['title'], '%url' => $feed['url'])));
747
748 case 200:
749 case 302:
750 case 307:
751 // Filter the input data:
752 if (aggregator_summary_parse_feed($result->data, $feed)) {
753
754 if ($result->headers['Last-Modified']) {
755 $modified = strtotime($result->headers['Last-Modified']);
756 }
757
758 /*
759 ** Prepare the channel data:
760 */
761
762 foreach ($channel as $key => $value) {
763 $channel[$key] = trim($value);
764 }
765
766 /*
767 ** Prepare the image data (if any):
768 */
769
770 foreach ($image as $key => $value) {
771 $image[$key] = trim($value);
772 }
773
774 if ($image['LINK'] && $image['URL'] && $image['TITLE']) {
775 // 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
776 $image = '<a href="'. check_url($image['LINK']) .'" class="feed-image"><img src="'. check_url($image['URL']) .'" alt="'. check_plain($image['TITLE']) .'" /></a>';
777 }
778 else {
779 $image = NULL;
780 }
781
782 /*
783 ** Update the feed data:
784 */
785
786 db_query("UPDATE {aggregator_summary_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']);
787
788 /*
789 ** Clear the cache:
790 */
791
792 cache_clear_all();
793
794 watchdog('aggregator', t('There is new syndicated content from %site.', array('%site' => $feed['title'])));
795 drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed['title'])));
796 }
797 break;
798 default:
799 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);
800 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)));
801 }
802 }
803
804 /**
805 * Parse the W3C date/time format, a subset of ISO 8601. PHP date parsing
806 * functions do not handle this format.
807 * See http://www.w3.org/TR/NOTE-datetime for more information.
808 * Originally from MagpieRSS (http://magpierss.sourceforge.net/).
809 *
810 * @param $date_str A string with a potentially W3C DTF date.
811 * @return A timestamp if parsed successfully or -1 if not.
812 */
813 function aggregator_summary_parse_w3cdtf($date_str) {
814 if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) {
815 list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
816 // calc epoch for current date assuming GMT
817 $epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year);
818 if ($match[10] != 'Z') { // Z is zulu time, aka GMT
819 list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]);
820 // zero out the variables
821 if (!$tz_hour) {
822 $tz_hour = 0;
823 }
824 if (!$tz_min) {
825 $tz_min = 0;
826 }
827 $offset_secs = (($tz_hour * 60) + $tz_min) * 60;
828 // is timezone ahead of GMT? then subtract offset
829 if ($tz_mod == '+') {
830 $offset_secs *= -1;
831 }
832 $epoch += $offset_secs;
833 }
834 return $epoch;
835 }
836 else {
837 return FALSE;
838 }
839 }
840
841 function aggregator_summary_parse_feed(&$data, $feed) {
842 global $items, $image, $channel;
843
844 // Unset the global variables before we use them:
845 unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']);
846 $items = array();
847 $image = array();
848 $channel = array();
849
850 // parse the data:
851 $xml_parser = drupal_xml_parser_create($data);
852 xml_set_element_handler($xml_parser, 'aggregator_summary_element_start', 'aggregator_summary_element_end');
853 xml_set_character_data_handler($xml_parser, 'aggregator_summary_element_data');
854
855 if (!xml_parse($xml_parser, $data, 1)) {
856 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);
857 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');
858 return 0;
859 }
860 xml_parser_free($xml_parser);
861
862 /*
863 ** We reverse the array such that we store the first item last,
864 ** and the last item first. In the database, the newest item
865 ** should be at the top.
866 */
867
868 $items = array_reverse($items);
869
870 // Initialize variables
871 $title = $link = $author = $description = $guid = NULL;
872 foreach ($items as $item) {
873 unset($title, $link, $author, $description, $guid);
874
875 // Prepare the item:
876 foreach ($item as $key => $value) {
877 $item[$key] = trim($value);
878 }
879
880 /*
881 ** Resolve the item's title. If no title is found, we use
882 ** up to 40 characters of the description ending at a word
883 ** boundary but not splitting potential entities.
884 */
885
886 if ($item['TITLE']) {
887 $title = $item['TITLE'];
888 }
889 else {
890 $title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['DESCRIPTION'], 40));
891 }
892
893 /*
894 ** Resolve the items link.
895 */
896
897 if ($item['LINK']) {
898 $link = $item['LINK'];
899 }
900 else {
901 $link = $feed['link'];
902 }
903 if ($item['GUID']) {
904 $guid = $item['GUID'];
905 }
906
907 /**
908 * Atom feeds have a CONTENT and/or SUMMARY tag instead of a DESCRIPTION tag
909 */
910 if ($item['CONTENT:ENCODED']) {
911 $item['DESCRIPTION'] = $item['CONTENT:ENCODED'];
912 }
913 else if ($item['SUMMARY']) {
914 $item['DESCRIPTION'] = $item['SUMMARY'];
915 }
916 else if ($item['CONTENT']) {
917 $item['DESCRIPTION'] = $item['CONTENT'];
918 }
919
920 /*
921 ** Try to resolve and parse the item's publication date. If no
922 ** date is found, we use the current date instead.
923 */
924
925 if ($item['PUBDATE']) $date = $item['PUBDATE']; // RSS 2.0
926 else if ($item['DC:DATE']) $date = $item['DC:DATE']; // Dublin core
927 else if ($item['DCTERMS:ISSUED']) $date = $item['DCTERMS:ISSUED']; // Dublin core
928 else if ($item['DCTERMS:CREATED']) $date = $item['DCTERMS:CREATED']; // Dublin core
929 else if ($item['DCTERMS:MODIFIED']) $date = $item['DCTERMS:MODIFIED']; // Dublin core
930 else if ($item['ISSUED']) $date = $item['ISSUED']; // Atom XML
931 else if ($item['CREATED']) $date = $item['CREATED']; // Atom XML
932 else if ($item['MODIFIED']) $date = $item['MODIFIED']; // Atom XML
933 else if ($item['PUBLISHED']) $date = $item['PUBLISHED']; // Atom XML
934 else if ($item['UPDATED']) $date = $item['UPDATED']; // Atom XML
935 else $date = 'now';
936
937 $timestamp = strtotime($date); // As of PHP 5.1.0, strtotime returns FALSE on failure instead of -1.
938 if ($timestamp <= 0) {
939 $timestamp = aggregator_summary_parse_w3cdtf($date); // Returns FALSE on failure
940 if (!$timestamp) {
941 $timestamp = time(); // better than nothing
942 }
943 }
944
945 /*
946 ** Save this item. Try to avoid duplicate entries as much as
947 ** possible. If we find a duplicate entry, we resolve it and
948 ** pass along it's ID such that we can update it if needed.
949 */
950
951 if ($guid) {
952 $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_summary_item} WHERE fid = %d AND guid = '%s'", $feed['fid'], $guid));
953 }
954 else if ($link && $link != $feed['link'] && $link != $feed['url']) {
955 $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_summary_item} WHERE fid = %d AND link = '%s'", $feed['fid'], $link));
956 }
957 else {
958 $entry = db_fetch_object(db_query("SELECT iid FROM {aggregator_summary_item} WHERE fid = %d AND title = '%s'", $feed['fid'], $title));
959 }
960
961 aggregator_summary_save_item(array('iid' => $entry->iid, 'fid' => $feed['fid'], 'timestamp' => $timestamp, 'title' => $title, 'link' => $link, 'author' => $item['AUTHOR'], 'description' => $item['DESCRIPTION'], 'guid' => $guid));
962 }
963
964 /*
965 ** Remove all items that are older than flush item timer:
966 */
967
968 $age = time() - variable_get('aggregator_summary_clear', 9676800);
969 $result = db_query('SELECT iid FROM {aggregator_summary_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
970
971 if (db_num_rows($result)) {
972 $items = array();
973 while ($item = db_fetch_object($result)) {
974 $items[] = $item->iid;
975 }
976 db_query('DELETE FROM {aggregator_summary_category_item} WHERE iid IN ('. implode(', ', $items) .')');
977 db_query('DELETE FROM {aggregator_summary_item} WHERE fid = %d AND timestamp < %d', $feed['fid'], $age);
978 }
979
980 return 1;
981 }
982
983 function aggregator_summary_save_item($edit) {
984 if ($edit['iid'] && $edit['title']) {
985 db_query("UPDATE {aggregator_summary_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['iid'], $edit['timestamp'], $edit['gid']);
986 }
987 else if ($edit['iid']) {
988 db_query('DELETE FROM {aggregator_summary_item} WHERE iid = %d', $edit['iid']);
989 db_query('DELETE FROM {aggregator_summary_category_item} WHERE iid = %d', $edit['iid']);
990 }
991 else if ($edit['title'] && $edit['link']) {
992 $edit['iid'] = db_next_id('{aggregator_summary_item}_iid');
993 db_query("INSERT INTO {aggregator_summary_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']);
994 // file the items in the categories indicated by the feed
995 $categories = db_query('SELECT cid FROM {aggregator_summary_category_feed} WHERE fid = %d', $edit['fid']);
996 while ($category = db_fetch_object($categories)) {
997 db_query('INSERT INTO {aggregator_summary_category_item} (cid, iid) VALUES (%d, %d)', $category->cid, $edit['iid']);
998 }
999 }
1000 }
1001
1002 function aggregator_summary_get_feed($fid) {
1003 return db_fetch_array(db_query('SELECT * FROM {aggregator_summary_feed} WHERE fid = %d', $fid));
1004 }
1005 function aggregator_summary_get_item($iid) {
1006 return db_fetch_array(db_query('SELECT * FROM {aggregator_summary_item} WHERE iid = %d', $iid));
1007 }
1008 function aggregator_summary_get_category($cid) {
1009 return db_fetch_array(db_query('SELECT * FROM {aggregator_summary_category} WHERE cid = %d', $cid));
1010 }
1011
1012 function aggregator_summary_view()
1013 {
1014 $result = db_query('SELECT f.*, COUNT(i.iid) AS items FROM {aggregator_summary_feed} f LEFT JOIN {aggregator_summary_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');
1015 $output = '<h3>'. t('Feed overview') .'</h3>';
1016 $header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '4'));
1017 $rows = array();
1018 while ($feed =