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

Line #Times calledCode
1
<?php
2
// $Id: aggregator.admin.inc,v 1.11 2008/08/03 05:46:55 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
 */
1270
function aggregator_admin_overview() {
1312
  return aggregator_view();
140
}
15
16
/**
17
 * Displays the aggregator administration page.
18
 *
19
 * @return
20
 *   The page HTML.
21
 */
2270
function aggregator_view() {
2312
  $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.etag,
f.modified, f.image, f.block ORDER BY f.title');
24
2512
  $output = '<h3>' . t('Feed overview') . '</h3>';
26
2712
  $header = array(t('Title'), t('Items'), t('Last update'), t('Next
update'), array('data' => t('Operations'), 'colspan' => '3'));
2812
  $rows = array();
2912
  while ($feed = db_fetch_object($result)) {
306
    $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(time() - $feed->checked)))
: t('never')), ($feed->checked ? t('%time left', array('%time' =>
format_interval($feed->checked + $feed->refresh - 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"));
316
  }
3212
  $output .= theme('table', $header, $rows);
33
3412
  $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
3612
  $output .= '<h3>' . t('Category overview') . '</h3>';
37
3812
  $header = array(t('Title'), t('Items'), t('Operations'));
3912
  $rows = array();
4012
  while ($category = db_fetch_object($result)) {
412
    $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"));
422
  }
4312
  $output .= theme('table', $header, $rows);
44
4512
  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
 */
5570
function aggregator_form_feed(&$form_state, $edit = array('refresh' => 900,
'title' => '', 'url' => '', 'fid' => NULL)) {
5633
  $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600,
32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200),
'format_interval');
57
5833
  if ($edit['refresh'] == '') {
590
    $edit['refresh'] = 3600;
600
  }
61
6233
  $form['title'] = array('#type' => 'textfield',
6333
    '#title' => t('Title'),
6433
    '#default_value' => $edit['title'],
6533
    '#maxlength' => 255,
6633
    '#description' => t('The name of the feed (or the name of the website
providing the feed).'),
6733
    '#required' => TRUE,
68
  );
6933
  $form['url'] = array('#type' => 'textfield',
7033
    '#title' => t('URL'),
7133
    '#default_value' => $edit['url'],
7233
    '#maxlength' => 255,
7333
    '#description' => t('The fully-qualified URL of the feed.'),
7433
    '#required' => TRUE,
75
  );
7633
  $form['refresh'] = array('#type' => 'select',
7733
    '#title' => t('Update interval'),
7833
    '#default_value' => $edit['refresh'],
7933
    '#options' => $period,
8033
    '#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
  );
82
83
  // Handling of categories.
8433
  $options = array();
8533
  $values = array();
8633
  $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 = %d ORDER BY title', $edit['fid']);
8733
  while ($category = db_fetch_object($categories)) {
886
    $options[$category->cid] = check_plain($category->title);
896
    if ($category->fid) $values[] = $category->cid;
906
  }
9133
  if ($options) {
926
    $form['category'] = array(
936
      '#type' => 'checkboxes',
946
      '#title' => t('Categorize news items'),
956
      '#default_value' => $values,
966
      '#options' => $options,
976
      '#description' => t('New feed items are automatically filed in the
checked categories.'),
98
    );
996
  }
10033
  $form['submit'] = array(
10133
    '#type' => 'submit',
10233
    '#value' => t('Save'),
103
  );
104
10533
  if ($edit['fid']) {
10614
    $form['delete'] = array(
10714
      '#type' => 'submit',
10814
      '#value' => t('Delete'),
109
    );
11014
    $form['fid'] = array(
11114
      '#type' => 'hidden',
11214
      '#value' => $edit['fid'],
113
    );
11414
  }
115
11633
  return $form;
1170
}
118
119
/**
120
 * Validate aggregator_form_feed() form submissions.
121
 */
12270
function aggregator_form_feed_validate($form, &$form_state) {
12314
  if ($form_state['values']['op'] == t('Save')) {
124
    // Ensure URL is valid.
1258
    if (!valid_url($form_state['values']['url'], TRUE)) {
1260
      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'])));
1270
    }
128
    // Check for duplicate titles.
1298
    if (isset($form_state['values']['fid'])) {
1301
      $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE
(title = '%s' OR url = '%s') AND fid <> %d",
$form_state['values']['title'], $form_state['values']['url'],
$form_state['values']['fid']);
1311
    }
132
    else {
1337
      $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE
title = '%s' OR url = '%s'", $form_state['values']['title'],
$form_state['values']['url']);
134
    }
1358
    while ($feed = db_fetch_object($result)) {
1360
      if (strcasecmp($feed->title, $form_state['values']['title']) == 0) {
1370
        form_set_error('title', t('A feed named %feed already exists.
Please enter a unique title.', array('%feed' =>
$form_state['values']['title'])));
1380
      }
1390
      if (strcasecmp($feed->url, $form_state['values']['url']) == 0) {
1400
        form_set_error('url', t('A feed with this URL %url already exists.
Please enter a unique URL.', array('%url' =>
$form_state['values']['url'])));
1410
      }
1420
    }
1438
  }
14414
}
145
146
/**
147
 * Process aggregator_form_feed() form submissions.
148
 *
149
 * @todo Add delete confirmation dialog.
150
 */
15170
function aggregator_form_feed_submit($form, &$form_state) {
15214
  if ($form_state['values']['op'] == t('Delete')) {
1536
    $title = $form_state['values']['title'];
154
    // Unset the title.
1556
    unset($form_state['values']['title']);
1566
  }
15714
  aggregator_save_feed($form_state['values']);
15814
  if (isset($form_state['values']['fid'])) {
1597
    if (isset($form_state['values']['title'])) {
1601
      drupal_set_message(t('The feed %feed has been updated.',
array('%feed' => $form_state['values']['title'])));
1611
      if (arg(0) == 'admin') {
1621
        $form_state['redirect'] = 'admin/content/aggregator/';
1631
        return;
1640
      }
165
      else {
1660
        $form_state['redirect'] = 'aggregator/sources/' .
$form_state['values']['fid'];
1670
        return;
168
      }
1690
    }
170
    else {
1716
      watchdog('aggregator', 'Feed %feed deleted.', array('%feed' =>
$title));
1726
      drupal_set_message(t('The feed %feed has been deleted.',
array('%feed' => $title)));
1736
      if (arg(0) == 'admin') {
1746
        $form_state['redirect'] = 'admin/content/aggregator/';
1756
        return;
1760
      }
177
      else {
1780
        $form_state['redirect'] = 'aggregator/sources/';
1790
        return;
180
      }
181
    }
1820
  }
183
  else {
1847
    watchdog('aggregator', 'Feed %feed added.', array('%feed' =>
$form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'),
'admin/content/aggregator'));
1857
    drupal_set_message(t('The feed %feed has been added.', array('%feed' =>
$form_state['values']['title'])));
186
  }
1877
}
188
18970
function aggregator_admin_remove_feed($form_state, $feed) {
1904
  return confirm_form(
191
    array(
192
      'feed' => array(
1934
        '#type' => 'value',
1944
        '#value' => $feed,
1954
      ),
1964
    ),
1974
    t('Are you sure you want to remove all items from the feed %feed?',
array('%feed' => $feed['title'])),
1984
    'admin/content/aggregator',
1994
    t('This action cannot be undone.'),
2004
    t('Remove items'),
2014
    t('Cancel')
2024
  );
2030
}
204
205
/**
206
 * Remove all items from a feed and redirect to the overview page.
207
 *
208
 * @param $feed
209
 *   An associative array describing the feed to be cleared.
210
 */
21170
function aggregator_admin_remove_feed_submit($form, &$form_state) {
2122
  aggregator_remove($form_state['values']['feed']);
2132
  $form_state['redirect'] = 'admin/content/aggregator';
2142
}
215
216
/**
217
 * Form builder; Generate a form to import feeds from OPML.
218
 *
219
 * @ingroup forms
220
 * @see aggregator_form_opml_validate()
221
 * @see aggregator_form_opml_submit()
222
 */
22370
function aggregator_form_opml(&$form_state) {
22416
  $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600,
32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200),
'format_interval');
225
22616
  $form['#attributes'] = array('enctype' => "multipart/form-data");
227
22816
  $form['upload'] = array(
22916
    '#type' => 'file',
23016
    '#title' => t('OPML File'),
23116
    '#description' => t('Upload an OPML file containing a list of feeds to
be imported.'),
232
  );
23316
  $form['remote'] = array(
23416
    '#type' => 'textfield',
23516
    '#title' => t('OPML Remote URL'),
23616
    '#description' => t('Enter the URL of an OPML file. This file will be
downloaded and processed only once on submission of the form.'),
237
  );
23816
  $form['refresh'] = array(
23916
    '#type' => 'select',
24016
    '#title' => t('Update interval'),
24116
    '#default_value' => 3600,
24216
    '#options' => $period,
24316
    '#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'))),
244
  );
245
246
  // Handling of categories.
24716
  $options = array();
24816
  $categories = db_query('SELECT cid, title FROM {aggregator_category}
ORDER BY title');
24916
  while ($category = db_fetch_object($categories)) {
25016
    $options[$category->cid] = check_plain($category->title);
25116
  }
25216
  if ($options) {
25316
    $form['category'] = array(
25416
      '#type' => 'checkboxes',
25516
      '#title' => t('Categorize news items'),
25616
      '#options' => $options,
25716
      '#description' => t('New feed items are automatically filed in the
checked categories.'),
258
    );
25916
  }
26016
  $form['submit'] = array(
26116
    '#type' => 'submit',
26216
    '#value' => t('Import')
26316
  );
264
26516
  return $form;
2660
}
267
268
/**
269
 * Validate aggregator_form_opml form submissions.
270
 */
27170
function aggregator_form_opml_validate($form, &$form_state) {
272
  // If both fields are empty or filled, cancel.
2736
  if (empty($form_state['values']['remote']) ==
empty($_FILES['files']['name']['upload'])) {
2742
    form_set_error('remote', t('You must <em>either</em> upload a file or
enter a URL.'));
2752
  }
276
277
  // Validate the URL, if one was entered.
2786
  if (!empty($form_state['values']['remote']) &&
!valid_url($form_state['values']['remote'], TRUE)) {
2791
    form_set_error('remote', t('This URL is not valid.'));
2801
  }
2816
}
282
283
/**
284
 * Process aggregator_form_opml form submissions.
285
 */
28670
function aggregator_form_opml_submit($form, &$form_state) {
2873
  $data = '';
2883
  if ($file = file_save_upload('upload')) {
2892
    $data = file_get_contents($file->filepath);
2902
  }
291
  else {
2921
    $response = drupal_http_request($form_state['values']['remote']);
2931
    if (!isset($response->error)) {
2941
      $data = $response->data;
2951
    }
296
  }
297
2983
  $feeds = _aggregator_parse_opml($data);
2993
  if (empty($feeds)) {
3002
    drupal_set_message(t('No new feed has been added.'));
3012
    return;
3020
  }
303
3041
  $form_state['values']['op'] = t('Save');
305
3061
  foreach ($feeds as $feed) {
3071
    $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE
title = '%s' OR url = '%s'", $feed['title'], $feed['url']);
3081
    $duplicate = FALSE;
3091
    while ($old = db_fetch_object($result)) {
3101
      if (strcasecmp($old->title, $feed['title']) == 0) {
3111
        drupal_set_message(t('A feed named %title already exists.',
array('%title' => $old->title)), 'warning');
3121
        $duplicate = TRUE;
3131
        continue;
3140
      }
3151
      if (strcasecmp($old->url, $feed['url']) == 0) {
3161
        drupal_set_message(t('A feed with the URL %url already exists.',
array('%url' => $old->url)), 'warning');
3171
        $duplicate = TRUE;
3181
        continue;
3190
      }
3200
    }
3211
    if (!$duplicate) {
3221
      $form_state['values']['title'] = $feed['title'];
3231
      $form_state['values']['url'] = $feed['url'];
3241
      drupal_execute('aggregator_form_feed', $form_state);
3251
    }
3261
  }
3271
}
328
329
/**
330
 * Parse an OPML file.
331
 *
332
 * Feeds are recognized as <outline> elements with the attributes
333
 * <em>text</em> and <em>xmlurl</em> set.
334
 *
335
 * @param $opml
336
 *   The complete contents of an OPML document.
337
 * @return
338
 *   An array of feeds, each an associative array with a <em>title</em>
and
339
 *   a <em>url</em> element, or NULL if the OPML document failed to be
parsed.
340
 *   An empty array will be returned if the document is valid but contains
341
 *   no feeds, as some OPML documents do.
342
 */
34370
function _aggregator_parse_opml($opml) {
3443
  $feeds = array();
3453
  $xml_parser = drupal_xml_parser_create($opml);
3463
  if (xml_parse_into_struct($xml_parser, $opml, $values)) {
3472
    foreach ($values as $entry) {
3482
      if ($entry['tag'] == 'OUTLINE' && isset($entry['attributes'])) {
3492
        $item = $entry['attributes'];
3502
        if (!empty($item['XMLURL'])) {
3511
          $feeds[] = array('title' => $item['TEXT'], 'url' =>
$item['XMLURL']);
3521
        }
3532
      }
3542
    }
3552
  }
3563
  xml_parser_free($xml_parser);
357
3583
  return $feeds;
3590
}
360
361
/**
362
 * Menu callback; refreshes a feed, then redirects to the overview page.
363
 *
364
 * @param $feed
365
 *   An associative array describing the feed to be refreshed.
366
 */
36770
function aggregator_admin_refresh_feed($feed) {
3683
  aggregator_refresh($feed);
3693
  drupal_goto('admin/content/aggregator');
3700
}
371
372
/**
373
 * Form builder; Configure the aggregator system.
374
 *
375
 * @ingroup forms
376
 * @see system_settings_form()
377
 */
37870
function aggregator_admin_settings() {
3790
  $items = array(0 => t('none')) + drupal_map_assoc(array(3, 5, 10, 15, 20,
25), '_aggregator_items');
3800
  $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400,
172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800),
'format_interval');
381
3820
  $form['aggregator_allowed_html_tags'] = array(
3830
    '#type' => 'textfield', '#title' => t('Allowed HTML tags'), '#size' =>
80, '#maxlength' => 255,
3840
    '#default_value' => variable_get('aggregator_allowed_html_tags', '<a>
<b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'),
3850
    '#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.)'),
386
  );
387
3880
  $form['aggregator_summary_items'] = array(
3890
    '#type' => 'select', '#title' => t('Items shown in sources and
categories pages') ,
3900
    '#default_value' => variable_get('aggregator_summary_items', 3),
'#options' => $items,
3910
    '#description' => t('Number of feed items displayed in feed and
category summary pages.'),
392
  );
393
3940
  $form['aggregator_clear'] = array(
3950
    '#type' => 'select', '#title' => t('Discard items older than'),
3960
    '#default_value' => variable_get('aggregator_clear', 9676800),
'#options' => $period,
3970
    '#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'))),
398
  );
399
4000
  $form['aggregator_category_selector'] = array(
4010
    '#type' => 'radios', '#title' => t('Category selection type'),
'#default_value' => variable_get('aggregator_category_selector',
'checkboxes'),
4020
    '#options' => array('checkboxes' => t('checkboxes'), 'select' =>
t('multiple selector')),
4030
    '#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 work well with large numbers of
categories.)'),
404
  );
405
4060
  return system_settings_form($form);
4070
}
408
409
/**
410
 * Form builder; Generate a form to add/edit/delete aggregator categories.
411
 *
412
 * @ingroup forms
413
 * @see aggregator_form_category_validate()
414
 * @see aggregator_form_category_submit()
415
 */
41670
function aggregator_form_category(&$form_state, $edit = array('title' =>
'', 'description' => '', 'cid' => NULL)) {
4173
  $form['title'] = array('#type' => 'textfield',
4183
    '#title' => t('Title'),
4193
    '#default_value' => $edit['title'],
4203
    '#maxlength' => 64,
4213
    '#required' => TRUE,
422
  );
4233
  $form['description'] = array('#type' => 'textarea',
4243
    '#title' => t('Description'),
4253
    '#default_value' => $edit['description'],
426
  );
4273
  $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
428
4293
  if ($edit['cid']) {
4300
    $form['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
4310
    $form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']);
4320
  }
433
4343
  return $form;
4350
}
436
437
/**
438
 * Validate aggregator_form_feed form submissions.
439
 */
44070
function aggregator_form_category_validate($form, &$form_state) {
4411
  if ($form_state['values']['op'] == t('Save')) {
442
    // Check for duplicate titles
4431
    if (isset($form_state['values']['cid'])) {
4440
      $category = db_fetch_object(db_query("SELECT cid FROM
{aggregator_category} WHERE title = '%s' AND cid <> %d",
$form_state['values']['title'], $form_state['values']['cid']));
4450
    }
446
    else {
4471
      $category = db_fetch_object(db_query("SELECT cid FROM
{aggregator_category} WHERE title = '%s'",
$form_state['values']['title']));
448
    }
4491
    if ($category) {
4500
      form_set_error('title', t('A category named %category already exists.
Please enter a unique title.', array('%category' =>
$form_state['values']['title'])));
4510
    }
4521
  }
4531
}
454
455
/**
456
 * Process aggregator_form_category form submissions.
457
 *
458
 * @todo Add delete confirmation dialog.
459
 */
46070
function aggregator_form_category_submit($form, &$form_state) {
4611
  if ($form_state['values']['op'] == t('Delete')) {
4620
    $title = $form_state['values']['title'];
463
    // Unset the title.
4640
    unset($form_state['values']['title']);
4650
  }
4661
  aggregator_save_category($form_state['values']);
4671
  if (isset($form_state['values']['cid'])) {
4680
    if (isset($form_state['values']['title'])) {
4690
      drupal_set_message(t('The category %category has been updated.',
array('%category' => $form_state['values']['title'])));
4700
      if (arg(0) == 'admin') {
4710
        $form_state['redirect'] = 'admin/content/aggregator/';
4720
        return;
4730
      }
474
      else {
4750
        $form_state['redirect'] = 'aggregator/categories/' .
$form_state['values']['cid'];
4760
        return;
477
      }
4780
    }
479
    else {
4800
      watchdog('aggregator', 'Category %category deleted.',
array('%category' => $title));
4810
      drupal_set_message(t('The category %category has been deleted.',
array('%category' => $title)));
4820
      if (arg(0) == 'admin') {
4830
        $form_state['redirect'] = 'admin/content/aggregator/';
4840
        return;
4850
      }
486
      else {
4870
        $form_state['redirect'] = 'aggregator/categories/';
4880
        return;
489
      }
490
    }
4910
  }
492
  else {
4931
    watchdog('aggregator', 'Category %category added.', array('%category'
=> $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'),
'admin/content/aggregator'));
4941
    drupal_set_message(t('The category %category has been added.',
array('%category' => $form_state['values']['title'])));
495
  }
4961
}
49770