|
1 <?php |
|
2 |
|
3 /** |
|
4 * @file |
|
5 * Used to aggregate syndicated content (RSS, RDF, and Atom). |
|
6 */ |
|
7 |
|
8 /** |
|
9 * Denotes that a feed's items should never expire. |
|
10 */ |
|
11 define('AGGREGATOR_CLEAR_NEVER', 0); |
|
12 |
|
13 /** |
|
14 * Implements hook_help(). |
|
15 */ |
|
16 function aggregator_help($path, $arg) { |
|
17 switch ($path) { |
|
18 case 'admin/help#aggregator': |
|
19 $output = ''; |
|
20 $output .= '<h3>' . t('About') . '</h3>'; |
|
21 $output .= '<p>' . t('The Aggregator module is an on-site syndicator and news reader that gathers and displays fresh content from RSS-, RDF-, and Atom-based feeds made available across the web. Thousands of sites (particularly news sites and blogs) publish their latest headlines in feeds, using a number of standardized XML-based formats. For more information, see the online handbook entry for <a href="@aggregator-module">Aggregator module</a>.', array('@aggregator-module' => 'http://drupal.org/documentation/modules/aggregator', '@aggregator' => url('aggregator'))) . '</p>'; |
|
22 $output .= '<h3>' . t('Uses') . '</h3>'; |
|
23 $output .= '<dl>'; |
|
24 $output .= '<dt>' . t('Viewing feeds') . '</dt>'; |
|
25 $output .= '<dd>' . t('Feeds contain published content, and may be grouped in categories, generally by topic. Users view feed content in the <a href="@aggregator">main aggregator display</a>, or by <a href="@aggregator-sources">their source</a> (usually via an RSS feed reader). The most recent content in a feed or category can be displayed as a block through the <a href="@admin-block">Blocks administration page</a>.', array('@aggregator' => url('aggregator'), '@aggregator-sources' => url('aggregator/sources'), '@admin-block' => url('admin/structure/block'))) . '</a></dd>'; |
|
26 $output .= '<dt>' . t('Adding, editing, and deleting feeds') . '</dt>'; |
|
27 $output .= '<dd>' . t('Administrators can add, edit, and delete feeds, and choose how often to check each feed for newly updated items on the <a href="@feededit">Feed aggregator administration page</a>.', array('@feededit' => url('admin/config/services/aggregator'))) . '</dd>'; |
|
28 $output .= '<dt>' . t('OPML integration') . '</dt>'; |
|
29 $output .= '<dd>' . t('A <a href="@aggregator-opml">machine-readable OPML file</a> of all feeds is available. OPML is an XML-based file format used to share outline-structured information such as a list of RSS feeds. Feeds can also be <a href="@import-opml">imported via an OPML file</a>.', array('@aggregator-opml' => url('aggregator/opml'), '@import-opml' => url('admin/config/services/aggregator'))) . '</dd>'; |
|
30 $output .= '<dt>' . t('Configuring cron') . '</dt>'; |
|
31 $output .= '<dd>' . t('A correctly configured <a href="@cron">cron maintenance task</a> is required to update feeds automatically.', array('@cron' => 'http://drupal.org/cron')) . '</dd>'; |
|
32 $output .= '</dl>'; |
|
33 return $output; |
|
34 case 'admin/config/services/aggregator': |
|
35 $output = '<p>' . t('Thousands of sites (particularly news sites and blogs) publish their latest headlines and posts in feeds, using a number of standardized XML-based formats. Formats supported by the aggregator include <a href="@rss">RSS</a>, <a href="@rdf">RDF</a>, and <a href="@atom">Atom</a>.', array('@rss' => 'http://cyber.law.harvard.edu/rss/', '@rdf' => 'http://www.w3.org/RDF/', '@atom' => 'http://www.atomenabled.org')) . '</p>'; |
|
36 $output .= '<p>' . t('Current feeds are listed below, and <a href="@addfeed">new feeds may be added</a>. For each feed or feed category, the <em>latest items</em> block may be enabled at the <a href="@block">blocks administration page</a>.', array('@addfeed' => url('admin/config/services/aggregator/add/feed'), '@block' => url('admin/structure/block'))) . '</p>'; |
|
37 return $output; |
|
38 case 'admin/config/services/aggregator/add/feed': |
|
39 return '<p>' . t('Add a feed in RSS, RDF or Atom format. A feed may only have one entry.') . '</p>'; |
|
40 case 'admin/config/services/aggregator/add/category': |
|
41 return '<p>' . t('Categories allow feed items from different feeds to be grouped together. For example, several sport-related feeds may belong to a category named <em>Sports</em>. Feed items may be grouped automatically (by selecting a category when creating or editing a feed) or manually (via the <em>Categorize</em> page available from feed item listings). Each category provides its own feed page and block.') . '</p>'; |
|
42 case 'admin/config/services/aggregator/add/opml': |
|
43 return '<p>' . t('<acronym title="Outline Processor Markup Language">OPML</acronym> is an XML format used to exchange multiple feeds between aggregators. A single OPML document may contain a collection of many feeds. Drupal can parse such a file and import all feeds at once, saving you the effort of adding them manually. You may either upload a local file from your computer or enter a URL where Drupal can download it.') . '</p>'; |
|
44 } |
|
45 } |
|
46 |
|
47 /** |
|
48 * Implements hook_theme(). |
|
49 */ |
|
50 function aggregator_theme() { |
|
51 return array( |
|
52 'aggregator_wrapper' => array( |
|
53 'variables' => array('content' => NULL), |
|
54 'file' => 'aggregator.pages.inc', |
|
55 'template' => 'aggregator-wrapper', |
|
56 ), |
|
57 'aggregator_categorize_items' => array( |
|
58 'render element' => 'form', |
|
59 'file' => 'aggregator.pages.inc', |
|
60 ), |
|
61 'aggregator_feed_source' => array( |
|
62 'variables' => array('feed' => NULL), |
|
63 'file' => 'aggregator.pages.inc', |
|
64 'template' => 'aggregator-feed-source', |
|
65 ), |
|
66 'aggregator_block_item' => array( |
|
67 'variables' => array('item' => NULL, 'feed' => 0), |
|
68 ), |
|
69 'aggregator_summary_items' => array( |
|
70 'variables' => array('summary_items' => NULL, 'source' => NULL), |
|
71 'file' => 'aggregator.pages.inc', |
|
72 'template' => 'aggregator-summary-items', |
|
73 ), |
|
74 'aggregator_summary_item' => array( |
|
75 'variables' => array('item' => NULL), |
|
76 'file' => 'aggregator.pages.inc', |
|
77 'template' => 'aggregator-summary-item', |
|
78 ), |
|
79 'aggregator_item' => array( |
|
80 'variables' => array('item' => NULL), |
|
81 'file' => 'aggregator.pages.inc', |
|
82 'template' => 'aggregator-item', |
|
83 ), |
|
84 'aggregator_page_opml' => array( |
|
85 'variables' => array('feeds' => NULL), |
|
86 'file' => 'aggregator.pages.inc', |
|
87 ), |
|
88 'aggregator_page_rss' => array( |
|
89 'variables' => array('feeds' => NULL, 'category' => NULL), |
|
90 'file' => 'aggregator.pages.inc', |
|
91 ), |
|
92 ); |
|
93 } |
|
94 |
|
95 /** |
|
96 * Implements hook_menu(). |
|
97 */ |
|
98 function aggregator_menu() { |
|
99 $items['admin/config/services/aggregator'] = array( |
|
100 'title' => 'Feed aggregator', |
|
101 'description' => "Configure which content your site aggregates from other sites, how often it polls them, and how they're categorized.", |
|
102 'page callback' => 'aggregator_admin_overview', |
|
103 'access arguments' => array('administer news feeds'), |
|
104 'weight' => 10, |
|
105 'file' => 'aggregator.admin.inc', |
|
106 ); |
|
107 $items['admin/config/services/aggregator/add/feed'] = array( |
|
108 'title' => 'Add feed', |
|
109 'page callback' => 'drupal_get_form', |
|
110 'page arguments' => array('aggregator_form_feed'), |
|
111 'access arguments' => array('administer news feeds'), |
|
112 'type' => MENU_LOCAL_ACTION, |
|
113 'file' => 'aggregator.admin.inc', |
|
114 ); |
|
115 $items['admin/config/services/aggregator/add/category'] = array( |
|
116 'title' => 'Add category', |
|
117 'page callback' => 'drupal_get_form', |
|
118 'page arguments' => array('aggregator_form_category'), |
|
119 'access arguments' => array('administer news feeds'), |
|
120 'type' => MENU_LOCAL_ACTION, |
|
121 'file' => 'aggregator.admin.inc', |
|
122 ); |
|
123 $items['admin/config/services/aggregator/add/opml'] = array( |
|
124 'title' => 'Import OPML', |
|
125 'page callback' => 'drupal_get_form', |
|
126 'page arguments' => array('aggregator_form_opml'), |
|
127 'access arguments' => array('administer news feeds'), |
|
128 'type' => MENU_LOCAL_ACTION, |
|
129 'file' => 'aggregator.admin.inc', |
|
130 ); |
|
131 $items['admin/config/services/aggregator/remove/%aggregator_feed'] = array( |
|
132 'title' => 'Remove items', |
|
133 'page callback' => 'drupal_get_form', |
|
134 'page arguments' => array('aggregator_admin_remove_feed', 5), |
|
135 'access arguments' => array('administer news feeds'), |
|
136 'file' => 'aggregator.admin.inc', |
|
137 ); |
|
138 $items['admin/config/services/aggregator/update/%aggregator_feed'] = array( |
|
139 'title' => 'Update items', |
|
140 'page callback' => 'aggregator_admin_refresh_feed', |
|
141 'page arguments' => array(5), |
|
142 'access arguments' => array('administer news feeds'), |
|
143 'file' => 'aggregator.admin.inc', |
|
144 ); |
|
145 $items['admin/config/services/aggregator/list'] = array( |
|
146 'title' => 'List', |
|
147 'type' => MENU_DEFAULT_LOCAL_TASK, |
|
148 'weight' => -10, |
|
149 ); |
|
150 $items['admin/config/services/aggregator/settings'] = array( |
|
151 'title' => 'Settings', |
|
152 'description' => 'Configure the behavior of the feed aggregator, including when to discard feed items and how to present feed items and categories.', |
|
153 'page callback' => 'drupal_get_form', |
|
154 'page arguments' => array('aggregator_admin_form'), |
|
155 'access arguments' => array('administer news feeds'), |
|
156 'type' => MENU_LOCAL_TASK, |
|
157 'file' => 'aggregator.admin.inc', |
|
158 ); |
|
159 $items['aggregator'] = array( |
|
160 'title' => 'Feed aggregator', |
|
161 'page callback' => 'aggregator_page_last', |
|
162 'access arguments' => array('access news feeds'), |
|
163 'weight' => 5, |
|
164 'file' => 'aggregator.pages.inc', |
|
165 ); |
|
166 $items['aggregator/sources'] = array( |
|
167 'title' => 'Sources', |
|
168 'page callback' => 'aggregator_page_sources', |
|
169 'access arguments' => array('access news feeds'), |
|
170 'file' => 'aggregator.pages.inc', |
|
171 ); |
|
172 $items['aggregator/categories'] = array( |
|
173 'title' => 'Categories', |
|
174 'page callback' => 'aggregator_page_categories', |
|
175 'access callback' => '_aggregator_has_categories', |
|
176 'file' => 'aggregator.pages.inc', |
|
177 ); |
|
178 $items['aggregator/rss'] = array( |
|
179 'title' => 'RSS feed', |
|
180 'page callback' => 'aggregator_page_rss', |
|
181 'access arguments' => array('access news feeds'), |
|
182 'type' => MENU_CALLBACK, |
|
183 'file' => 'aggregator.pages.inc', |
|
184 ); |
|
185 $items['aggregator/opml'] = array( |
|
186 'title' => 'OPML feed', |
|
187 'page callback' => 'aggregator_page_opml', |
|
188 'access arguments' => array('access news feeds'), |
|
189 'type' => MENU_CALLBACK, |
|
190 'file' => 'aggregator.pages.inc', |
|
191 ); |
|
192 $items['aggregator/categories/%aggregator_category'] = array( |
|
193 'title callback' => '_aggregator_category_title', |
|
194 'title arguments' => array(2), |
|
195 'page callback' => 'aggregator_page_category', |
|
196 'page arguments' => array(2), |
|
197 'access arguments' => array('access news feeds'), |
|
198 'file' => 'aggregator.pages.inc', |
|
199 ); |
|
200 $items['aggregator/categories/%aggregator_category/view'] = array( |
|
201 'title' => 'View', |
|
202 'type' => MENU_DEFAULT_LOCAL_TASK, |
|
203 'weight' => -10, |
|
204 ); |
|
205 $items['aggregator/categories/%aggregator_category/categorize'] = array( |
|
206 'title' => 'Categorize', |
|
207 'page callback' => 'drupal_get_form', |
|
208 'page arguments' => array('aggregator_page_category_form', 2), |
|
209 'access arguments' => array('administer news feeds'), |
|
210 'type' => MENU_LOCAL_TASK, |
|
211 'file' => 'aggregator.pages.inc', |
|
212 ); |
|
213 $items['aggregator/categories/%aggregator_category/configure'] = array( |
|
214 'title' => 'Configure', |
|
215 'page callback' => 'drupal_get_form', |
|
216 'page arguments' => array('aggregator_form_category', 2), |
|
217 'access arguments' => array('administer news feeds'), |
|
218 'type' => MENU_LOCAL_TASK, |
|
219 'weight' => 1, |
|
220 'file' => 'aggregator.admin.inc', |
|
221 ); |
|
222 $items['aggregator/sources/%aggregator_feed'] = array( |
|
223 'page callback' => 'aggregator_page_source', |
|
224 'page arguments' => array(2), |
|
225 'access arguments' => array('access news feeds'), |
|
226 'file' => 'aggregator.pages.inc', |
|
227 ); |
|
228 $items['aggregator/sources/%aggregator_feed/view'] = array( |
|
229 'title' => 'View', |
|
230 'type' => MENU_DEFAULT_LOCAL_TASK, |
|
231 'weight' => -10, |
|
232 ); |
|
233 $items['aggregator/sources/%aggregator_feed/categorize'] = array( |
|
234 'title' => 'Categorize', |
|
235 'page callback' => 'drupal_get_form', |
|
236 'page arguments' => array('aggregator_page_source_form', 2), |
|
237 'access arguments' => array('administer news feeds'), |
|
238 'type' => MENU_LOCAL_TASK, |
|
239 'file' => 'aggregator.pages.inc', |
|
240 ); |
|
241 $items['aggregator/sources/%aggregator_feed/configure'] = array( |
|
242 'title' => 'Configure', |
|
243 'page callback' => 'drupal_get_form', |
|
244 'page arguments' => array('aggregator_form_feed', 2), |
|
245 'access arguments' => array('administer news feeds'), |
|
246 'type' => MENU_LOCAL_TASK, |
|
247 'weight' => 1, |
|
248 'file' => 'aggregator.admin.inc', |
|
249 ); |
|
250 $items['admin/config/services/aggregator/edit/feed/%aggregator_feed'] = array( |
|
251 'title' => 'Edit feed', |
|
252 'page callback' => 'drupal_get_form', |
|
253 'page arguments' => array('aggregator_form_feed', 6), |
|
254 'access arguments' => array('administer news feeds'), |
|
255 'file' => 'aggregator.admin.inc', |
|
256 ); |
|
257 $items['admin/config/services/aggregator/edit/category/%aggregator_category'] = array( |
|
258 'title' => 'Edit category', |
|
259 'page callback' => 'drupal_get_form', |
|
260 'page arguments' => array('aggregator_form_category', 6), |
|
261 'access arguments' => array('administer news feeds'), |
|
262 'file' => 'aggregator.admin.inc', |
|
263 ); |
|
264 |
|
265 return $items; |
|
266 } |
|
267 |
|
268 /** |
|
269 * Title callback: Returns a title for aggregator category pages. |
|
270 * |
|
271 * @param $category |
|
272 * An aggregator category. |
|
273 * |
|
274 * @return |
|
275 * A string with the aggregator category title. |
|
276 */ |
|
277 function _aggregator_category_title($category) { |
|
278 return $category['title']; |
|
279 } |
|
280 |
|
281 /** |
|
282 * Determines whether there are any aggregator categories. |
|
283 * |
|
284 * @return |
|
285 * TRUE if there is at least one category and the user has access to them; |
|
286 * FALSE otherwise. |
|
287 */ |
|
288 function _aggregator_has_categories() { |
|
289 return user_access('access news feeds') && (bool) db_query_range('SELECT 1 FROM {aggregator_category}', 0, 1)->fetchField(); |
|
290 } |
|
291 |
|
292 /** |
|
293 * Implements hook_permission(). |
|
294 */ |
|
295 function aggregator_permission() { |
|
296 return array( |
|
297 'administer news feeds' => array( |
|
298 'title' => t('Administer news feeds'), |
|
299 ), |
|
300 'access news feeds' => array( |
|
301 'title' => t('View news feeds'), |
|
302 ), |
|
303 ); |
|
304 } |
|
305 |
|
306 /** |
|
307 * Implements hook_cron(). |
|
308 * |
|
309 * Queues news feeds for updates once their refresh interval has elapsed. |
|
310 */ |
|
311 function aggregator_cron() { |
|
312 $result = db_query('SELECT * FROM {aggregator_feed} WHERE queued = 0 AND checked + refresh < :time AND refresh <> :never', array( |
|
313 ':time' => REQUEST_TIME, |
|
314 ':never' => AGGREGATOR_CLEAR_NEVER |
|
315 )); |
|
316 $queue = DrupalQueue::get('aggregator_feeds'); |
|
317 foreach ($result as $feed) { |
|
318 if ($queue->createItem($feed)) { |
|
319 // Add timestamp to avoid queueing item more than once. |
|
320 db_update('aggregator_feed') |
|
321 ->fields(array('queued' => REQUEST_TIME)) |
|
322 ->condition('fid', $feed->fid) |
|
323 ->execute(); |
|
324 } |
|
325 } |
|
326 |
|
327 // Remove queued timestamp after 6 hours assuming the update has failed. |
|
328 db_update('aggregator_feed') |
|
329 ->fields(array('queued' => 0)) |
|
330 ->condition('queued', REQUEST_TIME - (3600 * 6), '<') |
|
331 ->execute(); |
|
332 } |
|
333 |
|
334 /** |
|
335 * Implements hook_cron_queue_info(). |
|
336 */ |
|
337 function aggregator_cron_queue_info() { |
|
338 $queues['aggregator_feeds'] = array( |
|
339 'worker callback' => 'aggregator_refresh', |
|
340 'time' => 60, |
|
341 ); |
|
342 return $queues; |
|
343 } |
|
344 |
|
345 /** |
|
346 * Implements hook_block_info(). |
|
347 */ |
|
348 function aggregator_block_info() { |
|
349 $blocks = array(); |
|
350 $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title'); |
|
351 foreach ($result as $category) { |
|
352 $blocks['category-' . $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title)); |
|
353 } |
|
354 $result = db_query('SELECT fid, title FROM {aggregator_feed} WHERE block <> 0 ORDER BY fid'); |
|
355 foreach ($result as $feed) { |
|
356 $blocks['feed-' . $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title)); |
|
357 } |
|
358 return $blocks; |
|
359 } |
|
360 |
|
361 /** |
|
362 * Implements hook_block_configure(). |
|
363 */ |
|
364 function aggregator_block_configure($delta = '') { |
|
365 list($type, $id) = explode('-', $delta); |
|
366 if ($type == 'category') { |
|
367 $value = db_query('SELECT block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchField(); |
|
368 $form['block'] = array( |
|
369 '#type' => 'select', |
|
370 '#title' => t('Number of news items in block'), |
|
371 '#default_value' => $value, |
|
372 '#options' => drupal_map_assoc(range(2, 20)), |
|
373 ); |
|
374 return $form; |
|
375 } |
|
376 } |
|
377 |
|
378 /** |
|
379 * Implements hook_block_save(). |
|
380 */ |
|
381 function aggregator_block_save($delta = '', $edit = array()) { |
|
382 list($type, $id) = explode('-', $delta); |
|
383 if ($type == 'category') { |
|
384 db_update('aggregator_category') |
|
385 ->fields(array('block' => $edit['block'])) |
|
386 ->condition('cid', $id) |
|
387 ->execute(); |
|
388 } |
|
389 } |
|
390 |
|
391 /** |
|
392 * Implements hook_block_view(). |
|
393 * |
|
394 * Generates blocks for the latest news items in each category and feed. |
|
395 */ |
|
396 function aggregator_block_view($delta = '') { |
|
397 if (user_access('access news feeds')) { |
|
398 $block = array(); |
|
399 list($type, $id) = explode('-', $delta); |
|
400 $result = FALSE; |
|
401 switch ($type) { |
|
402 case 'feed': |
|
403 if ($feed = db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE block <> 0 AND fid = :fid', array(':fid' => $id))->fetchObject()) { |
|
404 $block['subject'] = check_plain($feed->title); |
|
405 $result = db_query_range("SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC", 0, $feed->block, array(':fid' => $id)); |
|
406 $read_more = theme('more_link', array('url' => 'aggregator/sources/' . $feed->fid, 'title' => t("View this feed's recent news."))); |
|
407 } |
|
408 break; |
|
409 |
|
410 case 'category': |
|
411 if ($category = db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchObject()) { |
|
412 $block['subject'] = check_plain($category->title); |
|
413 $result = db_query_range('SELECT i.* FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON ci.iid = i.iid WHERE ci.cid = :cid ORDER BY i.timestamp DESC, i.iid DESC', 0, $category->block, array(':cid' => $category->cid)); |
|
414 $read_more = theme('more_link', array('url' => 'aggregator/categories/' . $category->cid, 'title' => t("View this category's recent news."))); |
|
415 } |
|
416 break; |
|
417 } |
|
418 |
|
419 $items = array(); |
|
420 if (!empty($result)) { |
|
421 foreach ($result as $item) { |
|
422 $items[] = theme('aggregator_block_item', array('item' => $item)); |
|
423 } |
|
424 } |
|
425 |
|
426 // Only display the block if there are items to show. |
|
427 if (count($items) > 0) { |
|
428 $block['content'] = theme('item_list', array('items' => $items)) . $read_more; |
|
429 } |
|
430 return $block; |
|
431 } |
|
432 } |
|
433 |
|
434 /** |
|
435 * Adds/edits/deletes aggregator categories. |
|
436 * |
|
437 * @param $edit |
|
438 * An associative array describing the category to be added/edited/deleted. |
|
439 */ |
|
440 function aggregator_save_category($edit) { |
|
441 $link_path = 'aggregator/categories/'; |
|
442 if (!empty($edit['cid'])) { |
|
443 $link_path .= $edit['cid']; |
|
444 if (!empty($edit['title'])) { |
|
445 db_merge('aggregator_category') |
|
446 ->key(array('cid' => $edit['cid'])) |
|
447 ->fields(array( |
|
448 'title' => $edit['title'], |
|
449 'description' => $edit['description'], |
|
450 )) |
|
451 ->execute(); |
|
452 $op = 'update'; |
|
453 } |
|
454 else { |
|
455 db_delete('aggregator_category') |
|
456 ->condition('cid', $edit['cid']) |
|
457 ->execute(); |
|
458 // Remove category from feeds. |
|
459 db_delete('aggregator_category_feed') |
|
460 ->condition('cid', $edit['cid']) |
|
461 ->execute(); |
|
462 // Remove category from feed items. |
|
463 db_delete('aggregator_category_item') |
|
464 ->condition('cid', $edit['cid']) |
|
465 ->execute(); |
|
466 // Make sure there is no active block for this category. |
|
467 if (module_exists('block')) { |
|
468 db_delete('block') |
|
469 ->condition('module', 'aggregator') |
|
470 ->condition('delta', 'category-' . $edit['cid']) |
|
471 ->execute(); |
|
472 } |
|
473 $edit['title'] = ''; |
|
474 $op = 'delete'; |
|
475 } |
|
476 } |
|
477 elseif (!empty($edit['title'])) { |
|
478 // A single unique id for bundles and feeds, to use in blocks. |
|
479 $link_path .= db_insert('aggregator_category') |
|
480 ->fields(array( |
|
481 'title' => $edit['title'], |
|
482 'description' => $edit['description'], |
|
483 'block' => 5, |
|
484 )) |
|
485 ->execute(); |
|
486 $op = 'insert'; |
|
487 } |
|
488 if (isset($op)) { |
|
489 menu_link_maintain('aggregator', $op, $link_path, $edit['title']); |
|
490 } |
|
491 } |
|
492 |
|
493 /** |
|
494 * Add/edit/delete an aggregator feed. |
|
495 * |
|
496 * @param $edit |
|
497 * An associative array describing the feed to be added/edited/deleted. |
|
498 */ |
|
499 function aggregator_save_feed($edit) { |
|
500 if (!empty($edit['fid'])) { |
|
501 // An existing feed is being modified, delete the category listings. |
|
502 db_delete('aggregator_category_feed') |
|
503 ->condition('fid', $edit['fid']) |
|
504 ->execute(); |
|
505 } |
|
506 if (!empty($edit['fid']) && !empty($edit['title'])) { |
|
507 db_update('aggregator_feed') |
|
508 ->condition('fid', $edit['fid']) |
|
509 ->fields(array( |
|
510 'title' => $edit['title'], |
|
511 'url' => $edit['url'], |
|
512 'refresh' => $edit['refresh'], |
|
513 'block' => $edit['block'], |
|
514 )) |
|
515 ->execute(); |
|
516 } |
|
517 elseif (!empty($edit['fid'])) { |
|
518 $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $edit['fid']))->fetchCol(); |
|
519 if ($iids) { |
|
520 db_delete('aggregator_category_item') |
|
521 ->condition('iid', $iids, 'IN') |
|
522 ->execute(); |
|
523 } |
|
524 db_delete('aggregator_feed')-> |
|
525 condition('fid', $edit['fid']) |
|
526 ->execute(); |
|
527 db_delete('aggregator_item') |
|
528 ->condition('fid', $edit['fid']) |
|
529 ->execute(); |
|
530 // Make sure there is no active block for this feed. |
|
531 if (module_exists('block')) { |
|
532 db_delete('block') |
|
533 ->condition('module', 'aggregator') |
|
534 ->condition('delta', 'feed-' . $edit['fid']) |
|
535 ->execute(); |
|
536 } |
|
537 } |
|
538 elseif (!empty($edit['title'])) { |
|
539 $edit['fid'] = db_insert('aggregator_feed') |
|
540 ->fields(array( |
|
541 'title' => $edit['title'], |
|
542 'url' => $edit['url'], |
|
543 'refresh' => $edit['refresh'], |
|
544 'block' => $edit['block'], |
|
545 'link' => '', |
|
546 'description' => '', |
|
547 'image' => '', |
|
548 )) |
|
549 ->execute(); |
|
550 |
|
551 } |
|
552 if (!empty($edit['title'])) { |
|
553 // The feed is being saved, save the categories as well. |
|
554 if (!empty($edit['category'])) { |
|
555 foreach ($edit['category'] as $cid => $value) { |
|
556 if ($value) { |
|
557 db_insert('aggregator_category_feed') |
|
558 ->fields(array( |
|
559 'fid' => $edit['fid'], |
|
560 'cid' => $cid, |
|
561 )) |
|
562 ->execute(); |
|
563 } |
|
564 } |
|
565 } |
|
566 } |
|
567 } |
|
568 |
|
569 /** |
|
570 * Removes all items from a feed. |
|
571 * |
|
572 * @param $feed |
|
573 * An object describing the feed to be cleared. |
|
574 */ |
|
575 function aggregator_remove($feed) { |
|
576 _aggregator_get_variables(); |
|
577 // Call hook_aggregator_remove() on all modules. |
|
578 module_invoke_all('aggregator_remove', $feed); |
|
579 // Reset feed. |
|
580 db_update('aggregator_feed') |
|
581 ->condition('fid', $feed->fid) |
|
582 ->fields(array( |
|
583 'checked' => 0, |
|
584 'hash' => '', |
|
585 'etag' => '', |
|
586 'modified' => 0, |
|
587 )) |
|
588 ->execute(); |
|
589 } |
|
590 |
|
591 /** |
|
592 * Gets the fetcher, parser, and processors. |
|
593 * |
|
594 * @return |
|
595 * An array containing the fetcher, parser, and processors. |
|
596 */ |
|
597 function _aggregator_get_variables() { |
|
598 // Fetch the feed. |
|
599 $fetcher = variable_get('aggregator_fetcher', 'aggregator'); |
|
600 if ($fetcher == 'aggregator') { |
|
601 include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator.fetcher.inc'; |
|
602 } |
|
603 $parser = variable_get('aggregator_parser', 'aggregator'); |
|
604 if ($parser == 'aggregator') { |
|
605 include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator.parser.inc'; |
|
606 } |
|
607 $processors = variable_get('aggregator_processors', array('aggregator')); |
|
608 if (in_array('aggregator', $processors)) { |
|
609 include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator.processor.inc'; |
|
610 } |
|
611 return array($fetcher, $parser, $processors); |
|
612 } |
|
613 |
|
614 /** |
|
615 * Checks a news feed for new items. |
|
616 * |
|
617 * @param $feed |
|
618 * An object describing the feed to be refreshed. |
|
619 */ |
|
620 function aggregator_refresh($feed) { |
|
621 // Store feed URL to track changes. |
|
622 $feed_url = $feed->url; |
|
623 |
|
624 // Fetch the feed. |
|
625 list($fetcher, $parser, $processors) = _aggregator_get_variables(); |
|
626 $success = module_invoke($fetcher, 'aggregator_fetch', $feed); |
|
627 |
|
628 // We store the hash of feed data in the database. When refreshing a |
|
629 // feed we compare stored hash and new hash calculated from downloaded |
|
630 // data. If both are equal we say that feed is not updated. |
|
631 $hash = hash('sha256', $feed->source_string); |
|
632 |
|
633 if ($success && ($feed->hash != $hash)) { |
|
634 // Parse the feed. |
|
635 if (module_invoke($parser, 'aggregator_parse', $feed)) { |
|
636 // Update feed with parsed data. |
|
637 db_merge('aggregator_feed') |
|
638 ->key(array('fid' => $feed->fid)) |
|
639 ->fields(array( |
|
640 'url' => $feed->url, |
|
641 'link' => empty($feed->link) ? $feed->url : $feed->link, |
|
642 'description' => empty($feed->description) ? '' : $feed->description, |
|
643 'image' => empty($feed->image) ? '' : $feed->image, |
|
644 'hash' => $hash, |
|
645 'etag' => empty($feed->etag) ? '' : $feed->etag, |
|
646 'modified' => empty($feed->modified) ? 0 : $feed->modified, |
|
647 )) |
|
648 ->execute(); |
|
649 |
|
650 // Log if feed URL has changed. |
|
651 if ($feed->url != $feed_url) { |
|
652 watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed->title, '%url' => $feed->url)); |
|
653 } |
|
654 |
|
655 watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed->title)); |
|
656 drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed->title))); |
|
657 |
|
658 // If there are items on the feed, let all enabled processors do their work on it. |
|
659 if (@count($feed->items)) { |
|
660 foreach ($processors as $processor) { |
|
661 module_invoke($processor, 'aggregator_process', $feed); |
|
662 } |
|
663 } |
|
664 } |
|
665 } |
|
666 else { |
|
667 drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed->title))); |
|
668 } |
|
669 |
|
670 // Regardless of successful or not, indicate that this feed has been checked. |
|
671 db_update('aggregator_feed') |
|
672 ->fields(array('checked' => REQUEST_TIME, 'queued' => 0)) |
|
673 ->condition('fid', $feed->fid) |
|
674 ->execute(); |
|
675 |
|
676 // Expire old feed items. |
|
677 if (function_exists('aggregator_expire')) { |
|
678 aggregator_expire($feed); |
|
679 } |
|
680 } |
|
681 |
|
682 /** |
|
683 * Loads an aggregator feed. |
|
684 * |
|
685 * @param $fid |
|
686 * The feed id. |
|
687 * |
|
688 * @return |
|
689 * An object describing the feed. |
|
690 */ |
|
691 function aggregator_feed_load($fid) { |
|
692 $feeds = &drupal_static(__FUNCTION__); |
|
693 if (!isset($feeds[$fid])) { |
|
694 $feeds[$fid] = db_query('SELECT * FROM {aggregator_feed} WHERE fid = :fid', array(':fid' => $fid))->fetchObject(); |
|
695 } |
|
696 |
|
697 return $feeds[$fid]; |
|
698 } |
|
699 |
|
700 /** |
|
701 * Loads an aggregator category. |
|
702 * |
|
703 * @param $cid |
|
704 * The category id. |
|
705 * |
|
706 * @return |
|
707 * An associative array describing the category. |
|
708 */ |
|
709 function aggregator_category_load($cid) { |
|
710 $categories = &drupal_static(__FUNCTION__); |
|
711 if (!isset($categories[$cid])) { |
|
712 $categories[$cid] = db_query('SELECT * FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $cid))->fetchAssoc(); |
|
713 } |
|
714 |
|
715 return $categories[$cid]; |
|
716 } |
|
717 |
|
718 /** |
|
719 * Returns HTML for an individual feed item for display in the block. |
|
720 * |
|
721 * @param $variables |
|
722 * An associative array containing: |
|
723 * - item: The item to be displayed. |
|
724 * - feed: Not used. |
|
725 * |
|
726 * @ingroup themeable |
|
727 */ |
|
728 function theme_aggregator_block_item($variables) { |
|
729 // Display the external link to the item. |
|
730 return '<a href="' . check_url($variables['item']->link) . '">' . check_plain($variables['item']->title) . "</a>\n"; |
|
731 } |
|
732 |
|
733 /** |
|
734 * Renders the HTML content safely, as allowed. |
|
735 * |
|
736 * @param $value |
|
737 * The content to be filtered. |
|
738 * |
|
739 * @return |
|
740 * The filtered content. |
|
741 */ |
|
742 function aggregator_filter_xss($value) { |
|
743 return filter_xss($value, preg_split('/\s+|<|>/', variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'), -1, PREG_SPLIT_NO_EMPTY)); |
|
744 } |
|
745 |
|
746 /** |
|
747 * Checks and sanitizes the aggregator configuration. |
|
748 * |
|
749 * Goes through all fetchers, parsers and processors and checks whether they |
|
750 * are available. If one is missing, resets to standard configuration. |
|
751 * |
|
752 * @return |
|
753 * TRUE if this function resets the configuration; FALSE if not. |
|
754 */ |
|
755 function aggregator_sanitize_configuration() { |
|
756 $reset = FALSE; |
|
757 list($fetcher, $parser, $processors) = _aggregator_get_variables(); |
|
758 if (!module_exists($fetcher)) { |
|
759 $reset = TRUE; |
|
760 } |
|
761 if (!module_exists($parser)) { |
|
762 $reset = TRUE; |
|
763 } |
|
764 foreach ($processors as $processor) { |
|
765 if (!module_exists($processor)) { |
|
766 $reset = TRUE; |
|
767 break; |
|
768 } |
|
769 } |
|
770 if ($reset) { |
|
771 variable_del('aggregator_fetcher'); |
|
772 variable_del('aggregator_parser'); |
|
773 variable_del('aggregator_processors'); |
|
774 return TRUE; |
|
775 } |
|
776 return FALSE; |
|
777 } |
|
778 |
|
779 /** |
|
780 * Helper function for drupal_map_assoc. |
|
781 * |
|
782 * @param $count |
|
783 * Items count. |
|
784 * |
|
785 * @return |
|
786 * A string that is plural-formatted as "@count items". |
|
787 */ |
|
788 function _aggregator_items($count) { |
|
789 return format_plural($count, '1 item', '@count items'); |
|
790 } |