Simpletest Coverage - modules/aggregator/aggregator.admin.inc

1 <?php
2 // $Id: aggregator.admin.inc,v 1.36 2009/07/30 19:24:20 dries Exp $
3
4 /**
5 * @file
6 * Admin page callbacks for the aggregator module.
7 */
8
9 /**
10 * Menu callback; displays the aggregator administration page.
11 */
12 function aggregator_admin_overview() {
13 return aggregator_view();
14 }
15
16 /**
17 * Displays the aggregator administration page.
18 *
19 * @return
20 * The page HTML.
21 */
22 function aggregator_view() {
23 $result = db_query('SELECT f.*, COUNT(i.iid) AS items FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.hash, f.etag, f.modified, f.image, f.block ORDER BY f.title');
24
25 $output = '<h3>' . t('Feed overview') . '</h3>';
26
27 $header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3'));
28 $rows = array();
29 foreach ($result as $feed) {
30 $rows[] = array(l($feed->title, "aggregator/sources/$feed->fid"), format_plural($feed->items, '1 item', '@count items'), ($feed->checked ? t('@time ago', array('@time' => format_interval(REQUEST_TIME - $feed->checked))) : t('never')), ($feed->checked && $feed->refresh ? t('%time left', array('%time' => format_interval($feed->checked + $feed->refresh - REQUEST_TIME))) : t('never')), l(t('edit'), "admin/settings/aggregator/edit/feed/$feed->fid"), l(t('remove items'), "admin/settings/aggregator/remove/$feed->fid"), l(t('update items'), "admin/settings/aggregator/update/$feed->fid"));
31 }
32 if (empty($rows)) {
33 $rows[] = array(array('data' => t('No feeds available. <a href="@link">Add feed</a>.', array('@link' => url('admin/settings/aggregator/add/feed'))), 'colspan' => '5', 'class' => 'message'));
34 }
35 $output .= theme('table', $header, $rows);
36
37 $result = db_query('SELECT c.cid, c.title, COUNT(ci.iid) as items FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid GROUP BY c.cid, c.title ORDER BY title');
38
39 $output .= '<h3>' . t('Category overview') . '</h3>';
40
41 $header = array(t('Title'), t('Items'), t('Operations'));
42 $rows = array();
43 foreach ($result as $category) {
44 $rows[] = array(l($category->title, "aggregator/categories/$category->cid"), format_plural($category->items, '1 item', '@count items'), l(t('edit'), "admin/settings/aggregator/edit/category/$category->cid"));
45 }
46 if (empty($rows)) {
47 $rows[] = array(array('data' => t('No categories available. <a href="@link">Add category</a>.', array('@link' => url('admin/settings/aggregator/add/category'))), 'colspan' => '5', 'class' => 'message'));
48 }
49 $output .= theme('table', $header, $rows);
50
51 return $output;
52 }
53
54 /**
55 * Form builder; Generate a form to add/edit feed sources.
56 *
57 * @ingroup forms
58 * @see aggregator_form_feed_validate()
59 * @see aggregator_form_feed_submit()
60 */
61 function aggregator_form_feed(&$form_state, stdClass $feed = NULL) {
62 $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
63 $period[AGGREGATOR_CLEAR_NEVER] = t('Never');
64
65 $form['title'] = array('#type' => 'textfield',
66 '#title' => t('Title'),
67 '#default_value' => isset($feed->title) ? $feed->title : '',
68 '#maxlength' => 255,
69 '#description' => t('The name of the feed (or the name of the website providing the feed).'),
70 '#required' => TRUE,
71 );
72 $form['url'] = array('#type' => 'textfield',
73 '#title' => t('URL'),
74 '#default_value' => isset($feed->url) ? $feed->url : '',
75 '#maxlength' => 255,
76 '#description' => t('The fully-qualified URL of the feed.'),
77 '#required' => TRUE,
78 );
79 $form['refresh'] = array('#type' => 'select',
80 '#title' => t('Update interval'),
81 '#default_value' => isset($feed->refresh) ? $feed->refresh : 3600,
82 '#options' => $period,
83 '#description' => t('The length of time between feed updates. Requires a correctly configured <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))),
84 );
85 $form['block'] = array('#type' => 'select',
86 '#title' => t('News items in block'),
87 '#default_value' => isset($feed->block) ? $feed->block : 5,
88 '#options' => drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)),
89 '#description' => t("Drupal can make a block with the most recent news items of this feed. You can <a href=\"@block-admin\">configure blocks</a> to be displayed in the sidebar of your page. This setting lets you configure the number of news items to show in this feed's block. If you choose '0' this feed's block will be disabled.", array('@block-admin' => url('admin/structure/block'))),
90 );
91
92 // Handling of categories.
93 $options = array();
94 $values = array();
95 $categories = db_query('SELECT c.cid, c.title, f.fid FROM {aggregator_category} c LEFT JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = :fid ORDER BY title', array(':fid' => isset($feed->fid) ? $feed->fid : NULL));
96 foreach ($categories as $category) {
97 $options[$category->cid] = check_plain($category->title);
98 if ($category->fid) $values[] = $category->cid;
99 }
100
101 if ($options) {
102 $form['category'] = array(
103 '#type' => 'checkboxes',
104 '#title' => t('Categorize news items'),
105 '#default_value' => $values,
106 '#options' => $options,
107 '#description' => t('New feed items are automatically filed in the checked categories.'),
108 );
109 }
110 $form['submit'] = array(
111 '#type' => 'submit',
112 '#value' => t('Save'),
113 );
114
115 if (!empty($feed->fid)) {
116 $form['delete'] = array(
117 '#type' => 'submit',
118 '#value' => t('Delete'),
119 );
120 $form['fid'] = array(
121 '#type' => 'hidden',
122 '#value' => $feed->fid,
123 );
124 }
125
126 return $form;
127 }
128
129 /**
130 * Validate aggregator_form_feed() form submissions.
131 */
132 function aggregator_form_feed_validate($form, &$form_state) {
133 if ($form_state['values']['op'] == t('Save')) {
134 // Ensure URL is valid.
135 if (!valid_url($form_state['values']['url'], TRUE)) {
136 form_set_error('url', t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $form_state['values']['url'])));
137 }
138 // Check for duplicate titles.
139 if (isset($form_state['values']['fid'])) {
140 $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE (title = :title OR url = :url) AND fid <> :fid", array(':title' => $form_state['values']['title'], ':url' => $form_state['values']['url'], ':fid' => $form_state['values']['fid']));
141 }
142 else {
143 $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = :title OR url = :url", array(':title' => $form_state['values']['title'], ':url' => $form_state['values']['url']));
144 }
145 foreach ($result as $feed) {
146 if (strcasecmp($feed->title, $form_state['values']['title']) == 0) {
147 form_set_error('title', t('A feed named %feed already exists. Please enter a unique title.', array('%feed' => $form_state['values']['title'])));
148 }
149 if (strcasecmp($feed->url, $form_state['values']['url']) == 0) {
150 form_set_error('url', t('A feed with this URL %url already exists. Please enter a unique URL.', array('%url' => $form_state['values']['url'])));
151 }
152 }
153 }
154 }
155
156 /**
157 * Process aggregator_form_feed() form submissions.
158 *
159 * @todo Add delete confirmation dialog.
160 */
161 function aggregator_form_feed_submit($form, &$form_state) {
162 if ($form_state['values']['op'] == t('Delete')) {
163 $title = $form_state['values']['title'];
164 // Unset the title.
165 unset($form_state['values']['title']);
166 }
167 aggregator_save_feed($form_state['values']);
168 if (isset($form_state['values']['fid'])) {
169 if (isset($form_state['values']['title'])) {
170 drupal_set_message(t('The feed %feed has been updated.', array('%feed' => $form_state['values']['title'])));
171 if (arg(0) == 'admin') {
172 $form_state['redirect'] = 'admin/settings/aggregator/';
173 return;
174 }
175 else {
176 $form_state['redirect'] = 'aggregator/sources/' . $form_state['values']['fid'];
177 return;
178 }
179 }
180 else {
181 watchdog('aggregator', 'Feed %feed deleted.', array('%feed' => $title));
182 drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => $title)));
183 if (arg(0) == 'admin') {
184 $form_state['redirect'] = 'admin/settings/aggregator/';
185 return;
186 }
187 else {
188 $form_state['redirect'] = 'aggregator/sources/';
189 return;
190 }
191 }
192 }
193 else {
194 watchdog('aggregator', 'Feed %feed added.', array('%feed' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/settings/aggregator'));
195 drupal_set_message(t('The feed %feed has been added.', array('%feed' => $form_state['values']['title'])));
196 }
197 }
198
199 function aggregator_admin_remove_feed($form_state, $feed) {
200 return confirm_form(
201 array(
202 'feed' => array(
203 '#type' => 'value',
204 '#value' => $feed,
205 ),
206 ),
207 t('Are you sure you want to remove all items from the feed %feed?', array('%feed' => $feed->title)),
208 'admin/settings/aggregator',
209 t('This action cannot be undone.'),
210 t('Remove items'),
211 t('Cancel')
212 );
213 }
214
215 /**
216 * Remove all items from a feed and redirect to the overview page.
217 *
218 * @param $feed
219 * An associative array describing the feed to be cleared.
220 */
221 function aggregator_admin_remove_feed_submit($form, &$form_state) {
222 aggregator_remove($form_state['values']['feed']);
223 $form_state['redirect'] = 'admin/settings/aggregator';
224 }
225
226 /**
227 * Form builder; Generate a form to import feeds from OPML.
228 *
229 * @ingroup forms
230 * @see aggregator_form_opml_validate()
231 * @see aggregator_form_opml_submit()
232 */
233 function aggregator_form_opml(&$form_state) {
234 $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
235
236 $form['upload'] = array(
237 '#type' => 'file',
238 '#title' => t('OPML File'),
239 '#description' => t('Upload an OPML file containing a list of feeds to be imported.'),
240 );
241 $form['remote'] = array(
242 '#type' => 'textfield',
243 '#title' => t('OPML Remote URL'),
244 '#maxlength' => 1024,
245 '#description' => t('Enter the URL of an OPML file. This file will be downloaded and processed only once on submission of the form.'),
246 );
247 $form['refresh'] = array(
248 '#type' => 'select',
249 '#title' => t('Update interval'),
250 '#default_value' => 3600,
251 '#options' => $period,
252 '#description' => t('The length of time between feed updates. Requires a correctly configured <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))),
253 );
254 $form['block'] = array('#type' => 'select',
255 '#title' => t('News items in block'),
256 '#default_value' => 5,
257 '#options' => drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)),
258 '#description' => t("Drupal can make a block with the most recent news items of a feed. You can <a href=\"@block-admin\">configure blocks</a> to be displayed in the sidebar of your page. This setting lets you configure the number of news items to show in a feed's block. If you choose '0' these feeds' blocks will be disabled.", array('@block-admin' => url('admin/structure/block'))),
259 );
260
261 // Handling of categories.
262 $options = array_map('check_plain', db_query("SELECT cid, title FROM {aggregator_category} ORDER BY title")->fetchAllKeyed());
263 if ($options) {
264 $form['category'] = array(
265 '#type' => 'checkboxes',
266 '#title' => t('Categorize news items'),
267 '#options' => $options,
268 '#description' => t('New feed items are automatically filed in the checked categories.'),
269 );
270 }
271 $form['submit'] = array(
272 '#type' => 'submit',
273 '#value' => t('Import')
274 );
275
276 return $form;
277 }
278
279 /**
280 * Validate aggregator_form_opml form submissions.
281 */
282 function aggregator_form_opml_validate($form, &$form_state) {
283 // If both fields are empty or filled, cancel.
284 if (empty($form_state['values']['remote']) == empty($_FILES['files']['name']['upload'])) {
285 form_set_error('remote', t('You must <em>either</em> upload a file or enter a URL.'));
286 }
287
288 // Validate the URL, if one was entered.
289 if (!empty($form_state['values']['remote']) && !valid_url($form_state['values']['remote'], TRUE)) {
290 form_set_error('remote', t('This URL is not valid.'));
291 }
292 }
293
294 /**
295 * Process aggregator_form_opml form submissions.
296 */
297 function aggregator_form_opml_submit($form, &$form_state) {
298 $data = '';
299 if ($file = file_save_upload('upload')) {
300 $data = file_get_contents($file->filepath);
301 }
302 else {
303 $response = drupal_http_request($form_state['values']['remote']);
304 if (!isset($response->error)) {
305 $data = $response->data;
306 }
307 }
308
309 $feeds = _aggregator_parse_opml($data);
310 if (empty($feeds)) {
311 drupal_set_message(t('No new feed has been added.'));
312 return;
313 }
314
315 $form_state['values']['op'] = t('Save');
316
317 foreach ($feeds as $feed) {
318 // Ensure URL is valid.
319 if (!valid_url($feed['url'], TRUE)) {
320 drupal_set_message(t('The URL %url is invalid.', array('%url' => $feed['url'])), 'warning');
321 continue;
322 }
323
324 // Check for duplicate titles or URLs.
325 $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = :title OR url = :url", array(':title' => $feed['title'], ':url' => $feed['url']));
326 foreach ($result as $old) {
327 if (strcasecmp($old->title, $feed['title']) == 0) {
328 drupal_set_message(t('A feed named %title already exists.', array('%title' => $old->title)), 'warning');
329 continue 2;
330 }
331 if (strcasecmp($old->url, $feed['url']) == 0) {
332 drupal_set_message(t('A feed with the URL %url already exists.', array('%url' => $old->url)), 'warning');
333 continue 2;
334 }
335 }
336
337 $form_state['values']['title'] = $feed['title'];
338 $form_state['values']['url'] = $feed['url'];
339 drupal_form_submit('aggregator_form_feed', $form_state);
340 }
341
342 $form_state['redirect'] = 'admin/settings/aggregator';
343 }
344
345 /**
346 * Parse an OPML file.
347 *
348 * Feeds are recognized as <outline> elements with the attributes
349 * <em>text</em> and <em>xmlurl</em> set.
350 *
351 * @param $opml
352 * The complete contents of an OPML document.
353 * @return
354 * An array of feeds, each an associative array with a <em>title</em> and
355 * a <em>url</em> element, or NULL if the OPML document failed to be parsed.
356 * An empty array will be returned if the document is valid but contains
357 * no feeds, as some OPML documents do.
358 */
359 function _aggregator_parse_opml($opml) {
360 $feeds = array();
361 $xml_parser = drupal_xml_parser_create($opml);
362 if (xml_parse_into_struct($xml_parser, $opml, $values)) {
363 foreach ($values as $entry) {
364 if ($entry['tag'] == 'OUTLINE' && isset($entry['attributes'])) {
365 $item = $entry['attributes'];
366 if (!empty($item['XMLURL']) && !empty($item['TEXT'])) {
367 $feeds[] = array('title' => $item['TEXT'], 'url' => $item['XMLURL']);
368 }
369 }
370 }
371 }
372 xml_parser_free($xml_parser);
373
374 return $feeds;
375 }
376
377 /**
378 * Menu callback; refreshes a feed, then redirects to the overview page.
379 *
380 * @param $feed
381 * An object describing the feed to be refreshed.
382 */
383 function aggregator_admin_refresh_feed($feed) {
384 aggregator_refresh($feed);
385 drupal_goto('admin/settings/aggregator');
386 }
387
388 /**
389 * Form builder; Configure the aggregator system.
390 *
391 * @ingroup forms
392 */
393 function aggregator_admin_form($form_state) {
394
395 // Make sure configuration is sane.
396 aggregator_sanitize_configuration();
397
398 // Get all available fetchers.
399 $fetchers = module_implements('aggregator_fetch');
400 foreach ($fetchers as $k => $module) {
401 if ($info = module_invoke($module, 'aggregator_fetch_info')) {
402 $label = $info['title'] . ' <span class="description">' . $info['description'] . '</span>';
403 }
404 else {
405 $label = $module;
406 }
407 unset($fetchers[$k]);
408 $fetchers[$module] = $label;
409 }
410
411 // Get all available parsers.
412 $parsers = module_implements('aggregator_parse');
413 foreach ($parsers as $k => $module) {
414 if ($info = module_invoke($module, 'aggregator_parse_info')) {
415 $label = $info['title'] . ' <span class="description">' . $info['description'] . '</span>';
416 }
417 else {
418 $label = $module;
419 }
420 unset($parsers[$k]);
421 $parsers[$module] = $label;
422 }
423
424 // Get all available processors.
425 $processors = module_implements('aggregator_process');
426 foreach ($processors as $k => $module) {
427 if ($info = module_invoke($module, 'aggregator_process_info')) {
428 $label = $info['title'] . ' <span class="description">' . $info['description'] . '</span>';
429 }
430 else {
431 $label = $module;
432 }
433 unset($processors[$k]);
434 $processors[$module] = $label;
435 }
436
437 // Only show basic configuration if there are actually options.
438 $basic_conf = array();
439 if (count($fetchers) > 1) {
440 $basic_conf['aggregator_fetcher'] = array(
441 '#type' => 'radios',
442 '#title' => t('Fetcher'),
443 '#description' => t('Fetchers download data from an external source. Choose a fetcher suitable for the external source you would like to download from.'),
444 '#options' => $fetchers,
445 '#default_value' => variable_get('aggregator_fetcher', 'aggregator'),
446 );
447 }
448 if (count($parsers) > 1) {
449 $basic_conf['aggregator_parser'] = array(
450 '#type' => 'radios',
451 '#title' => t('Parser'),
452 '#description' => t('Parsers transform downloaded data into standard structures. Choose a parser suitable for the type of feeds you would like to aggregate.'),
453 '#options' => $parsers,
454 '#default_value' => variable_get('aggregator_parser', 'aggregator'),
455 );
456 }
457 if (count($processors) > 1) {
458 $basic_conf['aggregator_processors'] = array(
459 '#type' => 'checkboxes',
460 '#title' => t('Processors'),
461 '#description' => t('Processors act on parsed feed data, for example they store feed items. Choose the processors suitable for your task.'),
462 '#options' => $processors,
463 '#default_value' => variable_get('aggregator_processors', array('aggregator')),
464 );
465 }
466 if (count($basic_conf)) {
467 $form['basic_conf'] = array(
468 '#type' => 'fieldset',
469 '#title' => t('Basic configuration'),
470 '#description' => t('For most aggregation tasks, the default settings are fine.'),
471 '#collapsible' => TRUE,
472 '#collapsed' => FALSE,
473 );
474 $form['basic_conf'] += $basic_conf;
475 }
476
477 // Implementing modules will expect an array at $form['modules'].
478 $form['modules'] = array();
479
480 $form['submit'] = array(
481 '#type' => 'submit',
482 '#value' => t('Save configuration'),
483 );
484
485 return $form;
486 }
487
488 function aggregator_admin_form_submit($form, &$form_state) {
489 $form_state['values']['aggregator_processors'] = array_filter($form_state['values']['aggregator_processors']);
490 system_settings_form_submit($form, $form_state);
491 }
492
493 /**
494 * Form builder; Generate a form to add/edit/delete aggregator categories.
495 *
496 * @ingroup forms
497 * @see aggregator_form_category_validate()
498 * @see aggregator_form_category_submit()
499 */
500 function aggregator_form_category(&$form_state, $edit = array('title' => '', 'description' => '', 'cid' => NULL)) {
501 $form['title'] = array('#type' => 'textfield',
502 '#title' => t('Title'),
503 '#default_value' => $edit['title'],
504 '#maxlength' => 64,
505 '#required' => TRUE,
506 );
507 $form['description'] = array('#type' => 'textarea',
508 '#title' => t('Description'),
509 '#default_value' => $edit['description'],
510 );
511 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
512
513 if ($edit['cid']) {
514 $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
515 $form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']);
516 }
517
518 return $form;
519 }
520
521 /**
522 * Validate aggregator_form_feed form submissions.
523 */
524 function aggregator_form_category_validate($form, &$form_state) {
525 if ($form_state['values']['op'] == t('Save')) {
526 // Check for duplicate titles
527 if (isset($form_state['values']['cid'])) {
528 $category = db_query("SELECT cid FROM {aggregator_category} WHERE title = :title AND cid <> :cid", array(':title' => $form_state['values']['title'], ':cid' => $form_state['values']['cid']))->fetchObject();
529 }
530 else {
531 $category = db_query("SELECT cid FROM {aggregator_category} WHERE title = :title", array(':title' => $form_state['values']['title']))->fetchObject();
532 }
533 if ($category) {
534 form_set_error('title', t('A category named %category already exists. Please enter a unique title.', array('%category' => $form_state['values']['title'])));
535 }
536 }
537 }
538
539 /**
540 * Process aggregator_form_category form submissions.
541 *
542 * @todo Add delete confirmation dialog.
543 */
544 function aggregator_form_category_submit($form, &$form_state) {
545 if ($form_state['values']['op'] == t('Delete')) {
546 $title = $form_state['values']['title'];
547 // Unset the title.
548 unset($form_state['values']['title']);
549 }
550 aggregator_save_category($form_state['values']);
551 if (isset($form_state['values']['cid'])) {
552 if (isset($form_state['values']['title'])) {
553 drupal_set_message(t('The category %category has been updated.', array('%category' => $form_state['values']['title'])));
554 if (arg(0) == 'admin') {
555 $form_state['redirect'] = 'admin/settings/aggregator/';
556 return;
557 }
558 else {
559 $form_state['redirect'] = 'aggregator/categories/' . $form_state['values']['cid'];
560 return;
561 }
562 }
563 else {
564 watchdog('aggregator', 'Category %category deleted.', array('%category' => $title));
565 drupal_set_message(t('The category %category has been deleted.', array('%category' => $title)));
566 if (arg(0) == 'admin') {
567 $form_state['redirect'] = 'admin/settings/aggregator/';
568 return;
569 }
570 else {
571 $form_state['redirect'] = 'aggregator/categories/';
572 return;
573 }
574 }
575 }
576 else {
577 watchdog('aggregator', 'Category %category added.', array('%category' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/settings/aggregator'));
578 drupal_set_message(t('The category %category has been added.', array('%category' => $form_state['values']['title'])));
579 }
580 }
581

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.