Code coverage for /20081101/modules/aggregator/aggregator.admin.inc

Line #Times calledCode
1
<?php
2
// $Id: aggregator.admin.inc,v 1.18 2008/10/22 18:29:28 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
 */
1277
function aggregator_admin_overview() {
1315
  return aggregator_view();
140
}
15
16
/**
17
 * Displays the aggregator administration page.
18
 *
19
 * @return
20
 *   The page HTML.
21
 */
2277
function aggregator_view() {
2315
  $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
2515
  $output = '<h3>' . t('Feed overview') . '</h3>';
26
2715
  $header = array(t('Title'), t('Items'), t('Last update'), t('Next
update'), array('data' => t('Operations'), 'colspan' => '3'));
2815
  $rows = array();
2915
  foreach ($result as $feed) {
309
    $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 ? t('%time left',
array('%time' => format_interval($feed->checked + $feed->refresh -
REQUEST_TIME))) : t('never')), l(t('edit'),
"admin/content/aggregator/edit/feed/$feed->fid"), l(t('remove items'),
"admin/content/aggregator/remove/$feed->fid"), l(t('update items'),
"admin/content/aggregator/update/$feed->fid"));
319
  }
3215
  $output .= theme('table', $header, $rows);
33
3415
  $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');
35
3615
  $output .= '<h3>' . t('Category overview') . '</h3>';
37
3815
  $header = array(t('Title'), t('Items'), t('Operations'));
3915
  $rows = array();
4015
  foreach ($result as $category) {
413
    $rows[] = array(l($category->title,
"aggregator/categories/$category->cid"), format_plural($category->items, '1
item', '@count items'), l(t('edit'),
"admin/content/aggregator/edit/category/$category->cid"));
423
  }
4315
  $output .= theme('table', $header, $rows);
44
4515
  return $output;
460
}
47
48
/**
49
 * Form builder; Generate a form to add/edit feed sources.
50
 *
51
 * @ingroup forms
52
 * @see aggregator_form_feed_validate()
53
 * @see aggregator_form_feed_submit()
54
 */
5577
function aggregator_form_feed(&$form_state, $edit = array('refresh' => 900,
'block' => 5, 'title' => '', 'url' => '', 'fid' => NULL)) {
5636
  $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600,
32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200),
'format_interval');
57
5836
  if ($edit['refresh'] == '') {
590
    $edit['refresh'] = 3600;
600
  }
61
6236
  $form['title'] = array('#type' => 'textfield',
6336
    '#title' => t('Title'),
6436
    '#default_value' => $edit['title'],
6536
    '#maxlength' => 255,
6636
    '#description' => t('The name of the feed (or the name of the website
providing the feed).'),
6736
    '#required' => TRUE,
68
  );
6936
  $form['url'] = array('#type' => 'textfield',
7036
    '#title' => t('URL'),
7136
    '#default_value' => $edit['url'],
7236
    '#maxlength' => 255,
7336
    '#description' => t('The fully-qualified URL of the feed.'),
7436
    '#required' => TRUE,
75
  );
7636
  $form['refresh'] = array('#type' => 'select',
7736
    '#title' => t('Update interval'),
7836
    '#default_value' => $edit['refresh'],
7936
    '#options' => $period,
8036
    '#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'))),
81
  );
8236
  $form['block'] = array('#type' => 'select',
8336
    '#title' => t('News items in block'),
8436
    '#default_value' => $edit['block'],
8536
    '#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)),
8636
    '#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/build/block'))),
87
  );
88
89
  // Handling of categories.
9036
  $options = array();
9136
  $values = array();
9236
  $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' => $edit['fid']));
9336
  foreach ($categories as $category) {
946
    $options[$category->cid] = check_plain($category->title);
956
    if ($category->fid) $values[] = $category->cid;
966
  }
9736
  if ($options) {
986
    $form['category'] = array(
996
      '#type' => 'checkboxes',
1006
      '#title' => t('Categorize news items'),
1016
      '#default_value' => $values,
1026
      '#options' => $options,
1036
      '#description' => t('New feed items are automatically filed in the
checked categories.'),
104
    );
1056
  }
10636
  $form['submit'] = array(
10736
    '#type' => 'submit',
10836
    '#value' => t('Save'),
109
  );
110
11136
  if ($edit['fid']) {
11214
    $form['delete'] = array(
11314
      '#type' => 'submit',
11414
      '#value' => t('Delete'),
115
    );
11614
    $form['fid'] = array(
11714
      '#type' => 'hidden',
11814
      '#value' => $edit['fid'],
119
    );
12014
  }
121
12236
  return $form;
1230
}
124
125
/**
126
 * Validate aggregator_form_feed() form submissions.
127
 */
12877
function aggregator_form_feed_validate($form, &$form_state) {
12915
  if ($form_state['values']['op'] == t('Save')) {
130
    // Ensure URL is valid.
1319
    if (!valid_url($form_state['values']['url'], TRUE)) {
1320
      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'])));
1330
    }
134
    // Check for duplicate titles.
1359
    if (isset($form_state['values']['fid'])) {
1361
      $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']));
1371
    }
138
    else {
1398
      $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']));
140
    }
1419
    foreach ($result as $feed) {
1420
      if (strcasecmp($feed->title, $form_state['values']['title']) == 0) {
1430
        form_set_error('title', t('A feed named %feed already exists.
Please enter a unique title.', array('%feed' =>
$form_state['values']['title'])));
1440
      }
1450
      if (strcasecmp($feed->url, $form_state['values']['url']) == 0) {
1460
        form_set_error('url', t('A feed with this URL %url already exists.
Please enter a unique URL.', array('%url' =>
$form_state['values']['url'])));
1470
      }
1480
    }
1499
  }
15015
}
151
152
/**
153
 * Process aggregator_form_feed() form submissions.
154
 *
155
 * @todo Add delete confirmation dialog.
156
 */
15777
function aggregator_form_feed_submit($form, &$form_state) {
15815
  if ($form_state['values']['op'] == t('Delete')) {
1596
    $title = $form_state['values']['title'];
160
    // Unset the title.
1616
    unset($form_state['values']['title']);
1626
  }
16315
  aggregator_save_feed($form_state['values']);
16415
  if (isset($form_state['values']['fid'])) {
1657
    if (isset($form_state['values']['title'])) {
1661
      drupal_set_message(t('The feed %feed has been updated.',
array('%feed' => $form_state['values']['title'])));
1671
      if (arg(0) == 'admin') {
1681
        $form_state['redirect'] = 'admin/content/aggregator/';
1691
        return;
1700
      }
171
      else {
1720
        $form_state['redirect'] = 'aggregator/sources/' .
$form_state['values']['fid'];
1730
        return;
174
      }
1750
    }
176
    else {
1776
      watchdog('aggregator', 'Feed %feed deleted.', array('%feed' =>
$title));
1786
      drupal_set_message(t('The feed %feed has been deleted.',
array('%feed' => $title)));
1796
      if (arg(0) == 'admin') {
1806
        $form_state['redirect'] = 'admin/content/aggregator/';
1816
        return;
1820
      }
183
      else {
1840
        $form_state['redirect'] = 'aggregator/sources/';
1850
        return;
186
      }
187
    }
1880
  }
189
  else {
1908
    watchdog('aggregator', 'Feed %feed added.', array('%feed' =>
$form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'),
'admin/content/aggregator'));
1918
    drupal_set_message(t('The feed %feed has been added.', array('%feed' =>
$form_state['values']['title'])));
192
  }
1938
}
194
19577
function aggregator_admin_remove_feed($form_state, $feed) {
1964
  return confirm_form(
197
    array(
198
      'feed' => array(
1994
        '#type' => 'value',
2004
        '#value' => $feed,
2014
      ),
2024
    ),
2034
    t('Are you sure you want to remove all items from the feed %feed?',
array('%feed' => $feed['title'])),
2044
    'admin/content/aggregator',
2054
    t('This action cannot be undone.'),
2064
    t('Remove items'),
2074
    t('Cancel')
2084
  );
2090
}
210
211
/**
212
 * Remove all items from a feed and redirect to the overview page.
213
 *
214
 * @param $feed
215
 *   An associative array describing the feed to be cleared.
216
 */
21777
function aggregator_admin_remove_feed_submit($form, &$form_state) {
2182
  aggregator_remove($form_state['values']['feed']);
2192
  $form_state['redirect'] = 'admin/content/aggregator';
2202
}
221
222
/**
223
 * Form builder; Generate a form to import feeds from OPML.
224
 *
225
 * @ingroup forms
226
 * @see aggregator_form_opml_validate()
227
 * @see aggregator_form_opml_submit()
228
 */
22977
function aggregator_form_opml(&$form_state) {
23015
  $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600,
32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200),
'format_interval');
231
23215
  $form['#attributes'] = array('enctype' => "multipart/form-data");
233
23415
  $form['upload'] = array(
23515
    '#type' => 'file',
23615
    '#title' => t('OPML File'),
23715
    '#description' => t('Upload an OPML file containing a list of feeds to
be imported.'),
238
  );
23915
  $form['remote'] = array(
24015
    '#type' => 'textfield',
24115
    '#title' => t('OPML Remote URL'),
24215
    '#description' => t('Enter the URL of an OPML file. This file will be
downloaded and processed only once on submission of the form.'),
243
  );
24415
  $form['refresh'] = array(
24515
    '#type' => 'select',
24615
    '#title' => t('Update interval'),
24715
    '#default_value' => 3600,
24815
    '#options' => $period,
24915
    '#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'))),
250
  );
25115
  $form['block'] = array('#type' => 'select',
25215
    '#title' => t('News items in block'),
25315
    '#default_value' => 5,
25415
    '#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)),
25515
    '#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/build/block'))),
256
  );
257
258
  // Handling of categories.
25915
  $options = array_map('check_plain', db_query("SELECT cid, title FROM
{aggregator_category} ORDER BY title")->fetchAllKeyed());
26015
  if ($options) {
26115
    $form['category'] = array(
26215
      '#type' => 'checkboxes',
26315
      '#title' => t('Categorize news items'),
26415
      '#options' => $options,
26515
      '#description' => t('New feed items are automatically filed in the
checked categories.'),
266
    );
26715
  }
26815
  $form['submit'] = array(
26915
    '#type' => 'submit',
27015
    '#value' => t('Import')
27115
  );
272
27315
  return $form;
2740
}
275
276
/**
277
 * Validate aggregator_form_opml form submissions.
278
 */
27977
function aggregator_form_opml_validate($form, &$form_state) {
280
  // If both fields are empty or filled, cancel.
2816
  if (empty($form_state['values']['remote']) ==
empty($_FILES['files']['name']['upload'])) {
2822
    form_set_error('remote', t('You must <em>either</em> upload a file or
enter a URL.'));
2832
  }
284
285
  // Validate the URL, if one was entered.
2866
  if (!empty($form_state['values']['remote']) &&
!valid_url($form_state['values']['remote'], TRUE)) {
2871
    form_set_error('remote', t('This URL is not valid.'));
2881
  }
2896
}
290
291
/**
292
 * Process aggregator_form_opml form submissions.
293
 */
29477
function aggregator_form_opml_submit($form, &$form_state) {
2953
  $data = '';
2963
  if ($file = file_save_upload('upload')) {
2972
    $data = file_get_contents($file->filepath);
2982
  }
299
  else {
3001
    $response = drupal_http_request($form_state['values']['remote']);
3011
    if (!isset($response->error)) {
3021
      $data = $response->data;
3031
    }
304
  }
305
3063
  $feeds = _aggregator_parse_opml($data);
3073
  if (empty($feeds)) {
3082
    drupal_set_message(t('No new feed has been added.'));
3092
    return;
3100
  }
311
3121
  $form_state['values']['op'] = t('Save');
313
3141
  foreach ($feeds as $feed) {
315
    // Ensure URL is valid.
3161
    if (!valid_url($feed['url'], TRUE)) {
3170
      drupal_set_message(t('The URL %url is invalid.', array('%url' =>
$feed['url'])), 'warning');
3180
      continue;
3190
    }
320
321
    // Check for duplicate titles or URLs.
3221
    $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE
title = :title OR url = :url", array(':title' => $feed['title'], ':url' =>
$feed['url']));
3231
    foreach ($result as $old) {
3241
      if (strcasecmp($old->title, $feed['title']) == 0) {
3251
        drupal_set_message(t('A feed named %title already exists.',
array('%title' => $old->title)), 'warning');
3261
        continue 2;
3270
      }
3281
      if (strcasecmp($old->url, $feed['url']) == 0) {
3291
        drupal_set_message(t('A feed with the URL %url already exists.',
array('%url' => $old->url)), 'warning');
3301
        continue 2;
3310
      }
3320
    }
333
3341
    $form_state['values']['title'] = $feed['title'];
3351
    $form_state['values']['url'] = $feed['url'];
3361
    drupal_execute('aggregator_form_feed', $form_state);
3371
  }
338
3391
  $form_state['redirect'] = 'admin/content/aggregator';
3401
}
341
342
/**
343
 * Parse an OPML file.
344
 *
345
 * Feeds are recognized as <outline> elements with the attributes
346
 * <em>text</em> and <em>xmlurl</em> set.
347
 *
348
 * @param $opml
349
 *   The complete contents of an OPML document.
350
 * @return
351
 *   An array of feeds, each an associative array with a <em>title</em> and
352
 *   a <em>url</em> element, or NULL if the OPML document failed to be
parsed.
353
 *   An empty array will be returned if the document is valid but contains
354
 *   no feeds, as some OPML documents do.
355
 */
35677
function _aggregator_parse_opml($opml) {
3573
  $feeds = array();
3583
  $xml_parser = drupal_xml_parser_create($opml);
3593
  if (xml_parse_into_struct($xml_parser, $opml, $values)) {
3602
    foreach ($values as $entry) {
3612
      if ($entry['tag'] == 'OUTLINE' && isset($entry['attributes'])) {
3622
        $item = $entry['attributes'];
3632
        if (!empty($item['XMLURL'])) {
3641
          $feeds[] = array('title' => $item['TEXT'], 'url' =>
$item['XMLURL']);
3651
        }
3662
      }
3672
    }
3682
  }
3693
  xml_parser_free($xml_parser);
370
3713
  return $feeds;
3720
}
373
374
/**
375
 * Menu callback; refreshes a feed, then redirects to the overview page.
376
 *
377
 * @param $feed
378
 *   An associative array describing the feed to be refreshed.
379
 */
38077
function aggregator_admin_refresh_feed($feed) {
3815
  aggregator_refresh($feed);
3825
  drupal_goto('admin/content/aggregator');
3830
}
384
385
/**
386
 * Form builder; Configure the aggregator system.
387
 *
388
 * @ingroup forms
389
 * @see system_settings_form()
390
 */
39177
function aggregator_admin_settings() {
3920
  $items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10, 15, 20,
25), '_aggregator_items');
3930
  $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400,
172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800),
'format_interval');
394
3950
  $form['aggregator_allowed_html_tags'] = array(
3960
    '#type' => 'textfield', '#title' => t('Allowed HTML tags'), '#size' =>
80, '#maxlength' => 255,
3970
    '#default_value' => variable_get('aggregator_allowed_html_tags', '<a>
<b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'),
3980
    '#description' => t('A space-separated list of HTML tags allowed in the
content of feed items. (Tags in this list are not removed by Drupal.)'),
399
  );
400
4010
  $form['aggregator_summary_items'] = array(
4020
    '#type' => 'select', '#title' => t('Items shown in sources and
categories pages') ,
4030
    '#default_value' => variable_get('aggregator_summary_items', 3),
'#options' => $items,
4040
    '#description' => t('Number of feed items displayed in feed and
category summary pages.'),
405
  );
406
4070
  $form['aggregator_clear'] = array(
4080
    '#type' => 'select', '#title' => t('Discard items older than'),
4090
    '#default_value' => variable_get('aggregator_clear', 9676800),
'#options' => $period,
4100
    '#description' => t('The length of time to retain feed items before
discarding. (Requires a correctly configured <a href="@cron">cron
maintenance task</a>.)', array('@cron' => url('admin/reports/status'))),
411
  );
412
4130
  $form['aggregator_category_selector'] = array(
4140
    '#type' => 'radios', '#title' => t('Category selection type'),
'#default_value' => variable_get('aggregator_category_selector',
'checkboxes'),
4150
    '#options' => array('checkboxes' => t('checkboxes'), 'select' =>
t('multiple selector')),
4160
    '#description' => t('The type of category selection widget displayed on
categorization pages. (For a small number of categories, checkboxes are
easier to use, while a multiple selector works well with large numbers of
categories.)'),
417
  );
418
4190
  return system_settings_form($form);
4200
}
421
422
/**
423
 * Form builder; Generate a form to add/edit/delete aggregator categories.
424
 *
425
 * @ingroup forms
426
 * @see aggregator_form_category_validate()
427
 * @see aggregator_form_category_submit()
428
 */
42977
function aggregator_form_category(&$form_state, $edit = array('title' =>
'', 'description' => '', 'cid' => NULL)) {
4303
  $form['title'] = array('#type' => 'textfield',
4313
    '#title' => t('Title'),
4323
    '#default_value' => $edit['title'],
4333
    '#maxlength' => 64,
4343
    '#required' => TRUE,
435
  );
4363
  $form['description'] = array('#type' => 'textarea',
4373
    '#title' => t('Description'),
4383
    '#default_value' => $edit['description'],
439
  );
4403
  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
441
4423
  if ($edit['cid']) {
4430
    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
4440
    $form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']);
4450
  }
446
4473
  return $form;
4480
}
449
450
/**
451
 * Validate aggregator_form_feed form submissions.
452
 */
45377
function aggregator_form_category_validate($form, &$form_state) {
4541
  if ($form_state['values']['op'] == t('Save')) {
455
    // Check for duplicate titles
4561
    if (isset($form_state['values']['cid'])) {
4570
      $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();
4580
    }
459
    else {
4601
      $category = db_query("SELECT cid FROM {aggregator_category} WHERE
title = :title", array(':title' =>
$form_state['values']['title']))->fetchObject();
461
    }
4621
    if ($category) {
4630
      form_set_error('title', t('A category named %category already exists.
Please enter a unique title.', array('%category' =>
$form_state['values']['title'])));
4640
    }
4651
  }
4661
}
467
468
/**
469
 * Process aggregator_form_category form submissions.
470
 *
471
 * @todo Add delete confirmation dialog.
472
 */
47377
function aggregator_form_category_submit($form, &$form_state) {
4741
  if ($form_state['values']['op'] == t('Delete')) {
4750
    $title = $form_state['values']['title'];
476
    // Unset the title.
4770
    unset($form_state['values']['title']);
4780
  }
4791
  aggregator_save_category($form_state['values']);
4801
  if (isset($form_state['values']['cid'])) {
4810
    if (isset($form_state['values']['title'])) {
4820
      drupal_set_message(t('The category %category has been updated.',
array('%category' => $form_state['values']['title'])));
4830
      if (arg(0) == 'admin') {
4840
        $form_state['redirect'] = 'admin/content/aggregator/';
4850
        return;
4860
      }
487
      else {
4880
        $form_state['redirect'] = 'aggregator/categories/' .
$form_state['values']['cid'];
4890
        return;
490
      }
4910
    }
492
    else {
4930
      watchdog('aggregator', 'Category %category deleted.',
array('%category' => $title));
4940
      drupal_set_message(t('The category %category has been deleted.',
array('%category' => $title)));
4950
      if (arg(0) == 'admin') {
4960
        $form_state['redirect'] = 'admin/content/aggregator/';
4970
        return;
4980
      }
499
      else {
5000
        $form_state['redirect'] = 'aggregator/categories/';
5010
        return;
502
      }
503
    }
5040
  }
505
  else {
5061
    watchdog('aggregator', 'Category %category added.', array('%category'
=> $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'),
'admin/content/aggregator'));
5071
    drupal_set_message(t('The category %category has been added.',
array('%category' => $form_state['values']['title'])));
508
  }
5091
}
51077