/[drupal]/contributions/modules/feedparser/feedmanager.module
ViewVC logotype

Contents of /contributions/modules/feedparser/feedmanager.module

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


Revision 1.47 - (show annotations) (download) (as text)
Thu Jan 4 01:56:33 2007 UTC (2 years, 10 months ago) by budda
Branch: MAIN
CVS Tags: DRUPAL-5--0-1-dev, HEAD
Branch point for: DRUPAL-5, DRUPAL-4-7
Changes since 1.46: +9 -4 lines
File MIME type: text/x-php
Removed error output print statement.
1 <?php
2 // $Id: feedmanager.module,v 1.46 2007/01/01 14:32:23 budda Exp $
3
4 /**
5 * Feed Manager
6 * @description: Provides a standard interface to manage RSS feeds
7 *
8 * @author: Mike Carter <mike @ www.ixis.co.uk/contact>
9 *
10 * @todo: link feedmanager_opml() sources page
11 */
12
13 /**
14 * Implementation of hook_help().
15 */
16 function feedmanager_help($section) {
17 switch ($section) {
18 case 'admin/help#aggregator':
19 $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>';
20 $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/block'), '%aggregator-opml' => url('aggregator/opml'))) .'</p>';
21 $output .= t('<p>You can</p>
22 <ul>
23 <li>administer your list of news feeds <a href="%admin-aggregator">administer &gt;&gt; aggregator</a>.</li>
24 <li>add a new feed <a href="%admin-aggregator-add-feed">administer &gt;&gt; aggregator &gt;&gt; add feed</a>.</li>
25 <li>add a new category <a href="%admin-aggregator-add-category">administer &gt;&gt; aggregator &gt;&gt; add category</a>.</li>
26 <li>configure global settings for the news aggregator <a href="%admin-settings-aggregator">administer &gt;&gt; settings &gt;&gt; aggregator</a>.</li>
27 <li>control access to the aggregator module through access permissions <a href="%admin-access">administer &gt;&gt; access control &gt;&gt; permissions</a>.</li>
28 <li>set permissions to access new feeds for user roles such as anonymous users at <a href="%admin-access">administer &gt;&gt; access control</a>.</li>
29 <li>view the <a href="%aggregator">aggregator page</a>.</li>
30 </ul>
31 ', array('%admin-aggregator' => url('admin/aggregator'), '%admin-aggregator-add-feed' => url('admin/aggregator/add/feed'), '%admin-aggregator-add-category' => url('admin/aggregator/add/category'), '%admin-settings-aggregator' => url('admin/settings/feedmanager'), '%admin-access' => url('admin/access'), '%aggregator' => url('aggregator')));
32 $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>';
33 return $output;
34 case 'admin/modules#description':
35 return t('Provides a management interface for RSS/RDF/Atom feeds.');
36 case 'admin/aggregator':
37 return t('<p>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>.</p>', array('%block' => url('admin/block')));
38 case 'admin/aggregator/add/feed':
39 return t('<p>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>');
40 case 'admin/aggregator/add/category':
41 return t('<p>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>');
42 }
43 }
44
45
46 /**
47 * Implementation of hook_menu().
48 */
49 function feedmanager_menu($may_cache) {
50 $items = array();
51 $edit = user_access('administer news feeds');
52 $view = user_access('access news feeds');
53
54 if ($may_cache) {
55 $items[] = array('path' => 'aggregator/sources',
56 'title' => t('sources'),
57 'callback' => 'feedmanager_page_sources',
58 'access' => $view);
59 $items[] = array('path' => 'admin/aggregator',
60 'title' => t('aggregator'),
61 'callback' => 'feedmanager_admin_overview',
62 'access' => $edit);
63 $items[] = array('path' => 'admin/aggregator/add/feed',
64 'title' => t('add feed'),
65 'callback' => 'feedmanager_form_feed',
66 'access' => $edit,
67 'type' => MENU_LOCAL_TASK);
68 $items[] = array('path' => 'admin/aggregator/remove',
69 'title' => t('remove items'),
70 'callback' => 'feedmanager_remove_confirm',
71 'access' => $edit,
72 'type' => MENU_CALLBACK);
73 $items[] = array('path' => 'admin/aggregator/update',
74 'title' => t('update items'),
75 'callback' => 'feedmanager_admin_refresh_feed',
76 'access' => $edit,
77 'type' => MENU_CALLBACK);
78 $items[] = array('path' => 'admin/aggregator/list',
79 'title' => t('list'),
80 'type' => MENU_DEFAULT_LOCAL_TASK,
81 'weight' => -10);
82 $items[] = array('path' => 'admin/settings/feedmanager',
83 'title' => t('FeedManager'),
84 'callback' => 'feedmanager_admin_settings_dispatcher',
85 'access' => user_access('administer site configuration'),
86 'type' => MENU_NORMAL_ITEM,
87 );
88 }
89 else if (arg(1) == 'aggregator' && is_numeric(arg(4))) {
90 if (arg(3) == 'feed') {
91 $feed = feedmanager_get_feed(arg(4));
92 if ($feed) {
93 $items[] = array('path' => 'admin/aggregator/edit/feed/'. $feed['fid'],
94 'title' => t('edit feed'),
95 'callback' => 'feedmanager_form_feed',
96 'callback arguments' => array($feed),
97 'access' => $edit,
98 'type' => MENU_CALLBACK);
99 }
100 }
101 }
102
103 return $items;
104 }
105
106
107 /**
108 * Admin settings
109 */
110 function feedmanager_admin_settings_dispatcher() {
111 if (strncmp(VERSION, '4', 1) == 0) {
112 $form_html = feedmanager_admin_settings();
113 } else {
114 $form_html = drupal_get_form('feedmanager_admin_settings');
115 }
116
117 return $form_html;
118 }
119
120 function feedmanager_admin_settings() {
121 $form['feedprocessing'] = array(
122 '#type' => 'fieldset',
123 '#title' => t('Feed Parsing')
124 );
125
126 $feedmanager_count['0'] = t('None');
127 $feedmanager_count += drupal_map_assoc(array(1, 2, 3, 4, 5, 10, 15, 20, 25, 50, 100));
128 $feedmanager_count['9999999'] = t('All');
129 $form['feedprocessing']['feedmanager_cron_count'] = array (
130 '#type' => 'select',
131 '#title' => t('Update count'),
132 '#default_value' => variable_get('feedmanager_cron_count', 5),
133 '#options' => $feedmanager_count,
134 '#description' => t('Select how many feeds can be updated in one cron run.')
135 );
136
137 $form['feedprocessing']['feedmanager_timeout'] = array(
138 '#type' => 'select',
139 '#options' => drupal_map_assoc(array(10,20,30,40,50,60)),
140 '#title' => t('Feed Timeout'),
141 '#description' => t('Define how many seconds to wait before giving up when attempting to load a feed.'),
142 '#default_value' => variable_get('feedmanager_timeout', 20)
143 );
144
145 $vocabularies = taxonomy_get_vocabularies();
146 $vocabularies_list = array();
147 foreach($vocabularies as $vocabulary) {
148 $vocabularies_list[$vocabulary->vid] = check_plain($vocabulary->name);
149 }
150
151 $form['feedprocessing']['feedmanager_vocabulary'] = array(
152 '#type' => 'select',
153 '#multiple' => FALSE,
154 '#title' => t('Vocabulary'),
155 '#default_value' => variable_get('feedmanager_vocabulary', FALSE),
156 '#description' => t('Used to store taxonomy terms in for feeds.'),
157 '#options' => $vocabularies_list,
158 );
159
160 $form['feedsources'] = array(
161 '#type' => 'fieldset',
162 '#title' => t('Feed Sources'),
163 '#description' => t('Control the layout of feed items on the %url pages.', array('%url' => l('aggregator sources', 'aggregator/sources')))
164 );
165
166 $items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_feedmanager_items');
167 $form['feedsources']['aggregator_summary_items'] = array(
168 '#type' => 'select',
169 '#title' => t('Items shown in sources and categories pages') ,
170 '#default_value' => variable_get('aggregator_summary_items', 3),
171 '#options' => $items,
172 '#description' => t('The number of items which will be shown with each feed or category in the feed and category summary pages.')
173 );
174
175 $form['fullstory'] = array(
176 '#type' => 'fieldset',
177 '#title' => t('Full Story links')
178 );
179
180 $form['fullstory']['feedmanager_showfullstory'] = array(
181 '#type' => 'checkbox',
182 '#title' => t('Show a full story link'),
183 '#description' => t('When available in the feed item, a link back to the original post will be displayed at the end of a node.'),
184 '#default_value' => variable_get('feedmanager_showfullstory', true),
185 );
186
187 $options = array(
188 'default' => t('Default (no target attribute)'),
189 '_top' => t('Open link in window root'),
190 '_blank' => t('Open link in new window'),
191 );
192 $form['fullstory']['feedmanager_target'] = array(
193 '#type' => 'radios',
194 '#title' => t('Link Target'),
195 '#default_value' => variable_get('feedmanager_target', 'default'),
196 '#options' => $options,
197 );
198 $form['fullstory']['feedmanager_rel'] = array(
199 '#type' => 'checkbox',
200 '#return_value' => 'nofollow',
201 '#prefix' => '<div class="form-item"><label>Nofollow Value: </label>',
202 '#suffix' => '</div>',
203 '#title' => t('Add rel=&quot;nofollow&quot; Attribute'),
204 '#description' => t('The <a href="http://en.wikipedia.org/wiki/Nofollow#rel.3D.22nofollow.22">rel=&quot;nofollow&quot; attribute</a> prevents some search engines from spidering entered links.'),
205 '#default_value' => variable_get('feedmanager_rel', false),
206 );
207
208 if (strncmp(VERSION, '4', 1) == 0) {
209 return system_settings_form('feedmanager_admin_settings', $form);
210 } else {
211 return system_settings_form($form);
212 }
213 }
214
215
216 /**
217 * Implementation of hook_perm().
218 */
219 function feedmanager_perm() {
220 return array('administer news feeds', 'access news feeds');
221 }
222
223
224 /**
225 * Generate a form to add/edit feed sources.
226 */
227 function feedmanager_form_feed($edit = array()) {
228 $op = arg(2);
229
230 switch($op) {
231 case 'edit':
232 $form['title'] = array('#type' => 'textfield',
233 '#title' => t('Title'),
234 '#default_value' => $edit['title'],
235 '#maxlength' => 64,
236 '#description' => t('The name of the feed; typically the name of the web site you syndicate content from.'),
237 '#required' => TRUE,
238 );
239
240 $form['url'] = array('#type' => 'textfield',
241 '#title' => t('URL'),
242 '#default_value' => $edit['url'],
243 '#size' => 100,
244 '#description' => t('The fully-qualified URL of the feed.'),
245 '#attributes' => array('readonly' => 'readonly')
246 );
247
248 $form['description'] = array('#type' => 'textarea',
249 '#title' => t('Description'),
250 '#default_value' => $edit['description'],
251 '#rows' => 5,
252 '#description' => t('As supplied by the RSS feed.'),
253 );
254
255 $period = array(0 => t('never')) + drupal_map_assoc(array(300, 900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
256 if ($edit['refresh'] == -1) {
257 $edit['refresh'] = 3600;
258 }
259 $form['refresh'] = array('#type' => 'select',
260 '#title' => t('Update interval'),
261 '#default_value' => $edit['refresh'],
262 '#options' => $period,
263 '#description' => t('The refresh interval indicating how often you want to update this feed. Requires %cron.', array('%cron' => l('crontab', 'admin/settings') )),
264 );
265
266 $period = array(0 => t('never')) + drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
267 $form['expires'] = array(
268 '#type' => 'select',
269 '#title' => t('Discard news items older than'),
270 '#default_value' => $edit['expires'],
271 '#options' => $period,
272 '#description' => t('Older news items will be automatically discarded. Requires %cron.', array('%cron' => l('crontab', 'admin/settings') ))
273 );
274
275 // Handling of categories:
276 $form += feedmanager_freetag($edit);
277
278 $form['advanced'] = array(
279 '#type' => 'fieldset',
280 '#title' => t('Advanced'),
281 '#description' => t('These settings are specific to the type of feed processor you have selected.'),
282 '#collapsible' => TRUE,
283 '#collapsed' => FALSE,
284 '#tree' => TRUE
285 );
286
287 $form['advanced']['update_items'] = array(
288 '#type' => 'checkbox',
289 '#title' => t('Update news items'),
290 '#description' => t('If the same news item is found in the feed on the next update the contents will overwrite the previous version stored here.'),
291 '#default_value' => $edit['update_items'] ? $edit['update_items'] : FALSE,
292 );
293
294 $form['advanced']['processor'] = array('#type' => 'value', '#value' => $edit['processor']);
295
296 $form['advanced']['stripads'] = array(
297 '#type' => 'checkbox',
298 '#title' => t('Strip Adverts'),
299 '#description' => t('Strip out ads from certain advertisers, namely Pheedo, Google AdSense, and certain types of Doubleclick ads.'),
300 '#default_value' => $edit['stripads'] ? $edit['stripads'] : TRUE,
301 );
302 break;
303
304 case 'add':
305 $form['url'] = array('#type' => 'textfield',
306 '#title' => t('URL'),
307 '#default_value' => $edit['url'],
308 '#size' => 100,
309 '#description' => t('The fully-qualified URL of the feed.'),
310 '#required' => TRUE,
311 );
312
313 // We set feeds to not update until they have been edited
314 $form['refresh'] = array('#type' => 'value', '#value' => -1);
315
316 $processors = module_invoke_all('feedapi', $feed, 'processor_name');
317 $form['processor'] = array(
318 '#type' => 'select',
319 '#title' => t('Processor'),
320 '#description' => t("Each feed can have it's items generated by a different processor. This allows you to generate different types of content per feed - such as nodes or traditional aggregator items. Re-edit the feed to see processor specific settings."),
321 '#options' => $processors,
322 '#default_value' => $edit['processor'],
323 '#size' => 1,
324 '#required' => TRUE,
325 );
326 break;
327 }
328
329 $form['submit'] = array(
330 '#type' => 'submit',
331 '#value' => t('Submit'),
332 '#weight' => 35,
333 );
334
335 if ($edit['fid']) {
336 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'), '#weight' => 19);
337 $form['fid'] = array('#type' => 'value', '#value' => $edit['fid']);
338 }
339
340 return drupal_get_form('feedmanager_form_feed_'.$op, $form);
341 }
342
343
344 /**
345 * Provides a freetagging form element.
346 *
347 * Code lifted from the taxonomy.module hook_form_alter()
348 */
349 function feedmanager_freetag(&$feed) {
350 $form = array();
351 $terms = $feed['taxonomy'];
352
353 // Find the vocabulary (if any) to use
354 $vid = variable_get('feedmanager_vocabulary', FALSE);
355
356 if($vid && $vocabulary = taxonomy_get_vocabulary($vid)) {
357 if($terms) {
358 $typed_terms = array();
359 foreach ($terms as $term) {
360 // Extract terms belonging to the vocabulary in question.
361 if ($term->vid == $vocabulary->vid) {
362
363 // Commas and quotes in terms are special cases, so encode 'em.
364 if (preg_match('/,/', $term->name) || preg_match('/"/', $term->name)) {
365 $term->name = '"'.preg_replace('/"/', '""', $term->name).'"';
366 }
367
368 $typed_terms[] = $term->name;
369 }
370 }
371
372 $typed_string = implode(', ', $typed_terms) . (array_key_exists('tags', $terms) ? $terms['tags'][$vocabulary->vid] : NULL);
373 }
374
375 if ($vocabulary->help) {
376 $help = $vocabulary->help;
377 }
378 else {
379 $help = t('A comma-separated list of terms describing this content. Example: funny, bungee jumping, "Company, Inc.".');
380 }
381
382 $form['taxonomy']['tags'] = array('#type' => 'textfield',
383 '#title' => $vocabulary->name,
384 '#description' => $help,
385 '#required' => $vocabulary->required,
386 '#default_value' => $typed_string,
387 '#autocomplete_path' => 'taxonomy/autocomplete/'. $vocabulary->vid,
388 '#weight' => $vocabulary->weight,
389 '#maxlength' => 255,
390 );
391 } else {
392 $form['taxonomy_help'] = array(
393 '#value' => t('You can automatically use tags defined in this feed to categories your nodes as they are created. To do this you need to %url to the %type content-type.', array('%url' => l(t('create a vocabulary and assign it'), 'admin/taxonomy'), '%type' => theme('placeholder', 'aggregator-item')))
394 );
395 }
396
397 return $form;
398 }
399
400 /**
401 * Validate feedmanager_form_feed form submissions.
402 */
403 function feedmanager_form_feed_add_validate($form_id, $form_values) {
404 // Check for duplicate feed urls
405 if (isset($form_values['fid'])) {
406 $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']);
407 }
408 else {
409 $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = '%s' OR url='%s'", $form_values['title'], $form_values['url']);
410 }
411 while ($feed = db_fetch_array($result)) {
412 if (strcasecmp($feed['url'], $form_values['url']) == 0) {
413 form_set_error('url', t('A feed with this URL %url already exists. Please enter a unique URL.', array('%url' => $form_values['url'])));
414 }
415 }
416 }
417
418
419 function feedmanager_form_feed_add_submit($form_id, $form_values) {
420 $feed = feedmanager_query_feed($form_values['url']);
421
422 // Create a Feed title if none is available
423 if(!$feed_title = $feed->get_feed_title()) {
424 $urlinfo = parse_url($form_values['url']);
425 $feed_title = $urlinfo['host'];
426 }
427
428 $form_values['title'] = $feed_title;
429 $form_values['description'] = $feed->get_feed_description();
430 $form_values['advanced']['processor'] = $form_values['processor'];
431 feedmanager_save_feed($form_values);
432
433 // Allow editing of further details
434 drupal_goto('admin/aggregator/edit/feed/'.$form_values['fid']);
435 }
436
437
438 /**
439 * Process feedmanager_form_feed form submissions.
440 * @todo Add delete confirmation dialog.
441 */
442 function feedmanager_form_feed_edit_submit($form_id, $form_values) {
443 if ($_POST['op'] == t('Delete')) {
444 $title = $form_values['title'];
445 // Unset the title:
446 unset($form_values['title']);
447 }
448
449 feedmanager_save_feed($form_values);
450
451 menu_rebuild();
452 if (isset($form_values['fid'])) {
453 if (isset($form_values['title'])) {
454 drupal_set_message(t('The feed %feed has been updated.', array('%feed' => theme('placeholder', $form_values['title']))));
455 if (arg(0) == 'admin') {
456 return 'admin/aggregator/';
457 }
458 else {
459 return 'aggregator/sources/'. $form_values['fid'];
460 }
461 }
462 else {
463 watchdog('aggregator', t('Feed %feed deleted.', array('%feed' => theme('placeholder', $title))));
464 drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => theme('placeholder', $title))));
465
466 module_invoke($form_values['processor'], 'delete_feed', $form_values);
467
468 if (arg(0) == 'admin') {
469 return 'admin/aggregator/';
470 }
471 else {
472 return 'aggregator/sources/';
473 }
474 }
475 }
476 else {
477 watchdog('aggregator', t('Feed %feed added.', array('%feed' => theme('placeholder', $form_values['title']))), WATCHDOG_NOTICE, l(t('view'), 'admin/aggregator'));
478 drupal_set_message(t('The feed %feed has been added.', array('%feed' => theme('placeholder', $form_values['title']))));
479 }
480 }
481
482
483
484 /**
485 * Add/edit/delete an aggregator feed.
486 */
487 function feedmanager_save_feed(&$edit) {
488 if ($edit['fid']) {
489 // An existing feed is being modified, delete the existing category listings.
490 db_query('DELETE FROM {aggregator_category_feed} WHERE fid = %d', $edit['fid']);
491 }
492 $data = serialize($edit['advanced']);
493
494 if ($edit['fid'] && $edit['title']) {
495 db_query("UPDATE {aggregator_feed} SET title = '%s', url = '%s', refresh = %d, description = '%s', expires = %d, data = '%s' WHERE fid = %d", $edit['title'], $edit['url'], $edit['refresh'], $edit['description'], $edit['expires'], $data, $edit['fid']);
496 }
497 else if ($edit['fid']) {
498
499 // Delete all feed category items
500 $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
501 while ($item = db_fetch_object($result)) {
502 $items[] = "iid = $item->iid";
503 }
504 if ($items) {
505 db_query('DELETE FROM {aggregator_category_item} WHERE '. implode(' OR ', $items));
506 }
507
508 db_query('DELETE FROM {aggregator_feed} WHERE fid = %d', $edit['fid']);
509 db_query('DELETE FROM {aggregator_item} WHERE fid = %d', $edit['fid']);
510 }
511 else if ($edit['title']) {
512 // A single unique id for bundles and feeds, to use in blocks.
513 $edit['fid'] = db_next_id('{aggregator_feed}_fid');
514 db_query("INSERT INTO {aggregator_feed} (fid, title, url, refresh, description, expires, data) VALUES (%d, '%s', '%s', %d, '%s', %d, '%s')", $edit['fid'], $edit['title'], $edit['url'], $edit['refresh'], $edit['description'], $edit['expires'], $data);
515 }
516
517 if ($edit['title']) {
518 // The feed is being saved, save the categories as well.
519
520 $vid = variable_get('feedmanager_vocabulary', FALSE);
521 if ($edit['tags'] && $vid) {
522
523 $vid_value = $edit['tags'];
524
525 // This regexp allows the following types of user input:
526 // this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
527 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
528 preg_match_all($regexp, $vid_value, $matches);
529 $typed_terms = array_unique($matches[1]);
530
531 $inserted = array();
532 foreach ($typed_terms as $typed_term) {
533 // If a user has escaped a term (to demonstrate that it is a group,
534 // or includes a comma or quote character), we remove the escape
535 // formatting so to save the term into the DB as the user intends.
536 $typed_term = str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $typed_term));
537 $typed_term = trim($typed_term);
538 if ($typed_term == "") { continue; }
539
540 // See if the term exists in the chosen vocabulary
541 // and return the tid, otherwise, add a new record.
542 $possibilities = taxonomy_get_term_by_name($typed_term);
543 $typed_term_tid = NULL; // tid match if any.
544 foreach ($possibilities as $possibility) {
545 if ($possibility->vid == $vid) {
546 $typed_term_tid = $possibility->tid;
547 }
548 }
549
550 // if the term does not already exist, add it to the feedmanager vocabulary
551 if (!$typed_term_tid) {
552 $term = array('vid' => $vid, 'name' => $typed_term);
553 $status = taxonomy_save_term($term);
554 $typed_term_tid = $term['tid'];
555 }
556
557 // defend against duplicate, different cased tags
558 if (!isset($inserted[$typed_term_tid])) {
559 db_query('INSERT INTO {aggregator_category_feed} (fid, cid) VALUES (%d, %d)', $edit['fid'], $typed_term_tid);
560 $inserted[$typed_term_tid] = TRUE;
561 }
562 }
563 }
564 } else {
565 drupal_set_message(t('Unable to add the feed due to incomplete details being provided.'));
566 }
567 }
568
569 function feedmanager_remove_confirm() {
570 $feed_id = arg(3);
571 $edit = $_POST['edit'];
572
573 $feed = feedmanager_get_feed($feed_id);
574
575 $form['feed_id'] = array('#type' => 'hidden', '#value' => $feed_id);
576 $form['operation'] = array('#type' => 'hidden', '#value' => 'remove');
577
578 $counter = module_invoke($feed['processor'], 'feedapi', $feed, 'item_count');
579 return confirm_form('feedmanager_remove_confirm', $form,
580 t('Are you sure you want to delete all %count?', array('%count' => format_plural($counter['item_count'], '1 item', '%count items'))),
581 'admin/node', t('This action cannot be undone.'),
582 t('Delete all'), t('Cancel'));
583 }
584
585
586 /**
587 * Removes all items (and associated data) from a feed
588 */
589 function feedmanager_remove_confirm_submit($form_id, $form_values) {
590 if ($form_values['confirm']) {
591 $feed = feedmanager_get_feed($form_values['feed_id']);
592 module_invoke_all('feedapi', $feed, 'remove');
593
594 db_query("UPDATE {aggregator_feed} SET checked = 0, etag = '', modified = 0 WHERE fid = %d", $feed['fid']);
595 drupal_set_message(t('The news items from %site have been removed.', array('%site' => theme('placeholder', $feed['title']))));
596 }
597 return 'admin/aggregator';
598 }
599
600
601 function feedmanager_query_feed($url) {
602 include_once 'simplepie.inc';
603
604 $pie_feed = new SimplePie();
605
606 $pie_feed->enable_caching(false);
607 $pie_feed->timeout = variable_get('feedmanager_timeout', 20);
608 $pie_feed->cache_max_minutes(240); // configuration option
609 $pie_feed->feed_url($url); // process the feed URL
610 $pie_feed->init(); // process the feed
611 $pie_feed->handle_content_type(); // set content type and character encoding
612
613 // Check if there's any problems loading the feed
614 $feed_error = $pie_feed->error;
615 if (isset($feed_error)) {
616 watchdog('aggregator', t('The RSS-feed from %site seems to be broken, due to "%error".', array('%site' => theme('placeholder', $pie_feed->get_feed_title()), '%error' => $feed_error)), WATCHDOG_WARNING);
617 drupal_set_message(t('The RSS-feed from %site seems to be broken, because of error "%error".', array('%site' => theme('placeholder', $pie_feed->get_feed_title()), '%error' => $feed_error)));
618 return FALSE;
619 }
620
621 return $pie_feed;
622 }
623
624
625 /**
626 * Checks a news feed for new items.
627 */
628 function feedmanager_refresh($feed) {
629 // Load The Feed
630 $pie_feed = feedmanager_query_feed($feed['url']);
631 if(!$pie_feed) return FALSE;
632
633 if (!$feed['data'] = $pie_feed->data) return FALSE;
634
635 $pie_feed->strip_ads($feed['stripads']); // configuration option
636
637 $feed['items'] = $pie_feed->get_items();
638
639 $feed_links = $pie_feed->get_feed_links();
640 $feed['link'] = array();
641 $feed['link']['self'] = $feed_links['self'] ? $feed_links['self'][0] : $feed['url'];
642 $feed['link']['alternate'] = $feed_links['alternate'] ? $feed_links['alternate'][0] : $feed['link']['self'];
643
644 $feed['description'] = $pie_feed->get_feed_description();
645
646 $feed['expires'] = 0;
647 $feed['modified'] = 0;
648 $feed['etag'] = 0;
649 // end simplepie specific
650
651 if ($pie_feed->get_image_link() && $pie_feed->get_image_url()) { // FIXME: use theme_image
652 $feed['image_html'] = '<a href="'. $pie_feed->get_image_link() .'" class="feed-image"><img src="'. check_url($pie_feed->get_image_url()) .'" alt="'. check_plain($pie_feed->get_image_title()) .'" /></a>';
653 }
654 else {
655 $feed['image_html'] = NULL;
656 }
657
658 // update feed information in database
659 db_query("UPDATE {aggregator_feed} SET url = '%s', checked = %d, link = '%s', description = '%s', image = '%s', etag = '%s', modified = %d WHERE fid = %d", $feed['link']['self'], time(), $feed['link']['alternate'], $feed['description'], $feed['image_html'], $feed['etag'], $feed['modified'], $feed['fid']);
660
661 // process all feed items
662 $processed_items = 0;
663 foreach($feed['items'] as $item) {
664 $item->data['iid'] = db_next_id('{feedparser}_iid');
665 $r = module_invoke_all('feedapi', $feed, 'item_save', $item);
666
667 // if an item is saved, increase counter
668 if($r['item_save']) {
669 $processed_items++;
670 }
671 }
672
673 // Log how many items were added and/or updated from a feed
674 if($processed_items > 0) {
675 watchdog('aggregator', t('There is %items new syndicated items from %site.', array('%items' => $processed_items, '%site' => theme('placeholder', $feed['title']))));
676 }
677
678 // Clear out any old feed items
679 if($feed['expires'] != 0) {
680 module_invoke($feed['processor'], 'feedapi', $feed, 'expire_items');
681 }
682 }
683
684
685 /**
686 * Gets information about a feed, either from the dbase or a temp cache
687 */
688 function feedmanager_get_feed($fid) {
689 static $feeds;
690
691 if(!$feeds[$fid] && $fid) {
692 $feed = db_fetch_array(db_query('SELECT * FROM {aggregator_feed} WHERE fid = %d', $fid));
693 $data = unserialize($feed['data']);
694 $feed += (array)$data;
695 unset($feed['data']);
696
697 $feed['taxonomy'] = array();
698
699 $terms = db_query('SELECT cid, fid FROM {aggregator_category_feed} WHERE fid = %d', $feed['fid']);
700 while ($term = db_fetch_object($terms)) {
701 if ($term->fid) $feed['taxonomy'][] = taxonomy_get_term($term->cid);
702 }
703
704 $feeds[$fid] = $feed;
705 }
706
707 return $feeds[$fid];
708 }
709
710
711 function feedmanager_view() {
712 $output .= '<h3>'. t('Feed overview') .'</h3>';
713
714 $header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3'));
715 $rows = array();
716
717 $result = db_query('SELECT fid FROM {aggregator_feed} ORDER BY title');
718 while ($feed = db_fetch_array($result)) {
719 $feed = feedmanager_get_feed($feed['fid']);
720
721 $item_count = module_invoke($feed['processor'], 'feedapi', $feed, 'item_count');
722
723 // If A Feed Processor Implements Listing of Items Provide A Link
724 if(module_hook($feed['processor'], 'list_items')) {
725 $item_list_link = l($feed['title'], "aggregator/sources/{$feed['fid']}");
726 } else {
727 $item_list_link = check_plain($feed['title']);
728 }
729
730 $rows[] = array(
731 $item_list_link,
732 format_plural($item_count['item_count'], '1 item', '%count items'),
733 ($feed['checked'] ? t('%time ago', array('%time' => format_interval(time() - $feed['checked']))) : t('never')),
734 ($feed['checked'] ? t('%time left', array('%time' => format_interval($feed['checked'] + $feed['refresh'] - time()))) : t('never')),
735 l(t('edit'), "admin/aggregator/edit/feed/{$feed['fid']}"),
736 l(t('remove items'), "admin/aggregator/remove/{$feed['fid']}"),
737 l(t('update items'), "admin/aggregator/update/{$feed['fid']}"));
738 }
739 $output .= theme('table', $header, $rows);
740
741 return $output;
742 }
743
744
745 /**
746 * Menu callback; removes all items from a feed, then redirects to the overview page.
747 */
748 function feedmanager_admin_remove_feed($feed) {
749 feedmanager_remove(feedmanager_get_feed($feed));
750 drupal_goto('admin/aggregator');
751 }
752
753
754 /**
755 * Menu callback; refreshes a feed, then redirects to the overview page.
756 */
757 function feedmanager_admin_refresh_feed($feed) {
758 feedmanager_refresh(feedmanager_get_feed($feed));
759 drupal_goto('admin/aggregator');
760 }
761
762
763 /**
764 * Menu callback; displays the aggregator administration page.
765 */
766 function feedmanager_admin_overview() {
767 return feedmanager_view();
768 }
769
770
771 /**
772 * Display items from a feed source
773 */
774 function feedmanager_page_sources() {
775 $fid = arg(2);
776
777 $output = '<div id="aggregator">';
778
779 // If A feed id is provided only show items from that feed
780 if(is_numeric($fid)) {
781 if($feed = feedmanager_get_feed($fid)) {
782 drupal_set_breadcrumb(array( l(t('Home'), NULL), l('Aggregator','aggregator/sources'), l($feed['title'], 'aggregator/sources/'.$feed['fid'] ) ));
783 $output .= theme('feedmanager_feed', $feed);
784 $output .= module_invoke($feed['processor'], 'list_items', $feed);
785 }
786 } else {
787 $result = db_query('SELECT fid FROM {aggregator_feed} ORDER BY title');
788 while ($fid = db_fetch_array($result)) {
789 $feed = feedmanager_get_feed($fid['fid']);
790 if(module_hook($feed['processor'], 'list_items')) {
791 $output .= '<h2>'. check_plain($feed['title']) ."</h2>\n";
792 $output .= module_invoke($feed['processor'], 'list_items', $feed, variable_get('aggregator_summary_items', 3));
793 //add more link to full page
794 }
795 }
796
797 // Add OPML XML link
798 }
799
800 $output .= '</div>';
801
802 return $output;
803 }
804
805
806 /**
807 * Generates an OPML representation of all feeds.
808 */
809 function feedmanager_opml() {
810 $result = db_query(db_rewrite_sql('SELECT title, url FROM {aggregator_feed} ORDER BY title ASC'));
811
812 $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
813 $output .= "<opml version=\"1.1\">\n";
814 $output .= "<head>\n";
815 $output .= '<title>'. variable_get('site_name', 'drupal') .' - '. variable_get('site_slogan', '') ."</title>\n";
816 $output .= '<dateModified>'. gmdate('r') ."</dateModified>\n";
817 $output .= "</head>\n";
818 $output .= "<body>\n";
819
820 while ($feed = db_fetch_object($result)) {
821 $output .= '<outline text="'. check_plain($feed->title) .'" xmlUrl="'. check_plain($feed->url) .'" />'."\n";
822 }
823
824 $output .= "</body>\n";
825 $output .= "</opml>\n";
826
827 drupal_set_header('Content-Type: text/xml; charset=utf-8');
828 print $output;
829 }
830
831
832 /**
833 * Implementation of hook_cron().
834 *
835 * Checks news feeds for updates once their refresh interval has elapsed.
836 */
837 function feedmanager_cron() {
838 // check how many feeds we can update at a time
839 $limit = variable_get('feedmanager_cron_count', 5);
840 if (is_numeric($limit) && $limit > -1) {
841 $limit = 'LIMIT '. $limit;
842 }
843 else {
844 $limit = '';
845 }
846
847 $result = db_query('SELECT fid FROM {aggregator_feed} WHERE refresh != 0 AND checked + refresh < %d ORDER BY checked ASC '. $limit, time());
848 while ($fid = db_fetch_array($result)) {
849 $feed = feedmanager_get_feed($fid['fid']);
850 feedmanager_refresh($feed);
851 }
852 }
853
854
855 /**
856 * Helper function for drupal_map_assoc.
857 */
858 function _feedmanager_items($count) {
859 return format_plural($count, '1 item', '%count items');
860 }
861
862
863 /**
864 * Safely render HTML content, as allowed.
865 */
866 function feedmanager_filter_xss($value) {
867 return filter_xss($value, preg_split('/\s+|<|>/', variable_get("feedmanager_allowed_html_tags", '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), -1, PREG_SPLIT_NO_EMPTY));
868 }
869
870
871 /**
872 * Private function;
873 * from: http://uk2.php.net/manual/en/function.html-entity-decode.php#51055
874 * Used as callback function for preg_replace_all() to decode numeric entities to UTF-8 chars
875 *
876 * @param $ord Number
877 * @return UTF-8 string
878 */
879 function _parse_num_entity($ord) {
880 $ord = $ord[1];
881 if (preg_match('/^x([0-9a-f]+)$/i', $ord, $match)) {
882 $ord = hexdec($match[1]);
883 }
884 else {
885 $ord = intval($ord);
886 }
887
888 $no_bytes = 0;
889 $byte = array();
890
891 if ($ord == 128) {
892 return chr(226).chr(130).chr(172);
893 }
894 else if($ord == 129) {
895 return chr(239).chr(191).chr(189);
896 }
897 else if($ord == 130) {
898 return chr(226).chr(128).chr(154);
899 }
900 else if($ord == 131) {
901 return chr(198).chr(146);
902 }
903 else if($ord == 132) {
904 return chr(226).chr(128).chr(158);
905 }
906 else if($ord == 133) {
907 return chr(226).chr(128).chr(166);
908 }
909 else if($ord == 134) {
910 return chr(226).chr(128).chr(160);
911 }
912 else if($ord == 135) {
913 return chr(226).chr(128).chr(161);
914 }
915 else if($ord == 136) {
916 return chr(203).chr(134);
917 }
918 else if($ord == 137) {
919 return chr(226).chr(128).chr(176);
920 }
921 else if($ord == 138) {
922 return chr(197).chr(160);
923 }
924 else if($ord == 139) {
925 return chr(226).chr(128).chr(185);
926 }
927 else if($ord == 140) {
928 return chr(197).chr(146);
929 }
930 else if($ord == 141) {
931 return chr(239).chr(191).chr(189);
932 }
933 else if($ord == 142) {
934 return chr(197).chr(189);
935 }
936 else if($ord == 143) {
937 return chr(239).chr(191).chr(189);
938 }
939 else if($ord == 144) {
940 return chr(239).chr(191).chr(189);
941 }
942 else if($ord == 145) {
943 return chr(226).chr(128).chr(152);
944 }
945 else if($ord == 146) {
946 return chr(226).chr(128).chr(153);
947 }
948 else if($ord == 147) {
949 return chr(226).chr(128).chr(156);
950 }
951 else if($ord == 148) {
952 return chr(226).chr(128).chr(157);
953 }
954 else if($ord == 149) {
955 return chr(226).chr(128).chr(162);
956 }
957 else if($ord == 150) {
958 return chr(226).chr(128).chr(147);
959 }
960 else if($ord == 151) {
961 return chr(226).chr(128).chr(148);
962 }
963 else if($ord == 152) {
964 return chr(203).chr(156);
965 }
966 else if($ord == 153) {
967 return chr(226).chr(132).chr(162);
968 }
969 else if($ord == 154) {
970 return chr(197).chr(161);
971 }
972 else if($ord == 155) {
973 return chr(226).chr(128).chr(186);
974 }
975 else if($ord == 156) {
976 return chr(197).chr(147);
977 }
978 else if($ord == 157) {
979 return chr(239).chr(191).chr(189);
980 }
981 else if($ord == 158) {
982 return chr(197).chr(190);
983 }
984 else if($ord == 159) {
985 return chr(197).chr(184);
986 }
987 else if($ord == 160) {
988 return chr(194).chr(160);
989 }
990
991 if ($ord < 128) {
992 return chr($ord);
993 }
994 else if ($ord < 2048) {
995 $no_bytes = 2;
996 }
997 else if ($ord < 65536) {
998 $no_bytes = 3;
999 }
1000 else if ($ord < 1114112) {
1001 $no_bytes = 4;
1002 }
1003 else {
1004 return;
1005 }
1006
1007 switch ($no_bytes) {
1008 case 2:
1009 $prefix = array(31, 192);
1010 break;
1011
1012 case 3:
1013 $prefix = array(15, 224);
1014 break;
1015
1016 case 4:
1017 $prefix = array(7, 240);
1018 break;
1019 }
1020
1021 for ($i = 0; $i < $no_bytes; $i++) {
1022 $byte[$no_bytes - $i - 1] = (($ord & (63 * pow(2, 6 * $i))) / pow(2, 6 * $i)) & 63 | 128;
1023 }
1024
1025 $byte[0] = ($byte[0] & $prefix[0]) | $prefix[1];
1026
1027 $ret = '';
1028 for ($i = 0; $i < $no_bytes; $i++) {
1029 $ret .= chr($byte[$i]);
1030 }
1031
1032 return $ret;
1033 }
1034
1035 /**
1036 * Private function; Convert named entities to UTF-8 characters
1037 * from: http://pl2.php.net/manual/en/function.html-entity-decode.php#51722
1038 */
1039 function _parse_name_entities(&$data) {
1040 static $ttr;
1041 if (!$ttr) {
1042 $trans_tbl = get_html_translation_table(HTML_ENTITIES);
1043 foreach ($trans_tbl as $k => $v) {
1044 $ttr[$v] = utf8_encode($k);
1045 }
1046 $ttr['&apos;'] = "'";
1047 }
1048 return strtr($data, $ttr);
1049 }
1050