summaryrefslogtreecommitdiff
path: root/kolab.org/www/drupal-7.26/modules/aggregator
diff options
context:
space:
mode:
Diffstat (limited to 'kolab.org/www/drupal-7.26/modules/aggregator')
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator-feed-source.tpl.php36
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator-item.tpl.php47
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator-rtl.css7
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator-summary-item.tpl.php25
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator-summary-items.tpl.php25
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator-wrapper.tpl.php20
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.admin.inc633
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.api.php220
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.css41
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.fetcher.inc61
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.info14
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.install330
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.module782
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.pages.inc582
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.parser.inc329
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.processor.inc213
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/aggregator.test1019
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test.info12
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test.module58
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test_atom.xml20
-rw-r--r--kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test_rss091.xml41
21 files changed, 4515 insertions, 0 deletions
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-feed-source.tpl.php b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-feed-source.tpl.php
new file mode 100644
index 0000000..f9cfa55
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-feed-source.tpl.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to present the source of the feed.
+ *
+ * The contents are rendered above feed listings when browsing source feeds.
+ * For example, "example.com/aggregator/sources/1".
+ *
+ * Available variables:
+ * - $source_icon: Feed icon linked to the source. Rendered through
+ * theme_feed_icon().
+ * - $source_image: Image set by the feed source.
+ * - $source_description: Description set by the feed source.
+ * - $source_url: URL to the feed source.
+ * - $last_checked: How long ago the feed was checked locally.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_aggregator_feed_source()
+ *
+ * @ingroup themeable
+ */
+?>
+<div class="feed-source">
+ <?php print $source_icon; ?>
+ <?php print $source_image; ?>
+ <div class="feed-description">
+ <?php print $source_description; ?>
+ </div>
+ <div class="feed-url">
+ <em><?php print t('URL:'); ?></em> <a href="<?php print $source_url; ?>"><?php print $source_url; ?></a>
+ </div>
+ <div class="feed-updated">
+ <em><?php print t('Updated:'); ?></em> <?php print $last_checked; ?>
+ </div>
+</div>
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-item.tpl.php b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-item.tpl.php
new file mode 100644
index 0000000..74b2284
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-item.tpl.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to format an individual feed item for display
+ * on the aggregator page.
+ *
+ * Available variables:
+ * - $feed_url: URL to the originating feed item.
+ * - $feed_title: Title of the feed item.
+ * - $source_url: Link to the local source section.
+ * - $source_title: Title of the remote source.
+ * - $source_date: Date the feed was posted on the remote source.
+ * - $content: Feed item content.
+ * - $categories: Linked categories assigned to the feed.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_aggregator_item()
+ *
+ * @ingroup themeable
+ */
+?>
+<div class="feed-item">
+ <h3 class="feed-item-title">
+ <a href="<?php print $feed_url; ?>"><?php print $feed_title; ?></a>
+ </h3>
+
+ <div class="feed-item-meta">
+ <?php if ($source_url): ?>
+ <a href="<?php print $source_url; ?>" class="feed-item-source"><?php print $source_title; ?></a> -
+ <?php endif; ?>
+ <span class="feed-item-date"><?php print $source_date; ?></span>
+ </div>
+
+<?php if ($content): ?>
+ <div class="feed-item-body">
+ <?php print $content; ?>
+ </div>
+<?php endif; ?>
+
+<?php if ($categories): ?>
+ <div class="feed-item-categories">
+ <?php print t('Categories'); ?>: <?php print implode(', ', $categories); ?>
+ </div>
+<?php endif ;?>
+
+</div>
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-rtl.css b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-rtl.css
new file mode 100644
index 0000000..057d015
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-rtl.css
@@ -0,0 +1,7 @@
+/**
+ * Right-to-Left styles for theme in the Aggregator module.
+ */
+
+#aggregator .feed-source .feed-icon {
+ float: left;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-summary-item.tpl.php b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-summary-item.tpl.php
new file mode 100644
index 0000000..f9199a5
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-summary-item.tpl.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to present a linked feed item for summaries.
+ *
+ * Available variables:
+ * - $feed_url: Link to originating feed.
+ * - $feed_title: Title of feed.
+ * - $feed_age: Age of remote feed.
+ * - $source_url: Link to remote source.
+ * - $source_title: Locally set title for the source.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_aggregator_summary_item()
+ *
+ * @ingroup themeable
+ */
+?>
+<a href="<?php print $feed_url; ?>"><?php print $feed_title; ?></a>
+<span class="age"><?php print $feed_age; ?></span>
+
+<?php if ($source_url): ?>,
+ <span class="source"><a href="<?php print $source_url; ?>"><?php print $source_title; ?></a></span>
+<?php endif; ?>
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-summary-items.tpl.php b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-summary-items.tpl.php
new file mode 100644
index 0000000..4a0551d
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-summary-items.tpl.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to present feeds as list items.
+ *
+ * Each iteration generates a single feed source or category.
+ *
+ * Available variables:
+ * - $title: Title of the feed or category.
+ * - $summary_list: Unordered list of linked feed items generated through
+ * theme_item_list().
+ * - $source_url: URL to the local source or category.
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_aggregator_summary_items()
+ *
+ * @ingroup themeable
+ */
+?>
+<h3><?php print $title; ?></h3>
+<?php print $summary_list; ?>
+<div class="links">
+ <a href="<?php print $source_url; ?>"><?php print t('More'); ?></a>
+</div>
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-wrapper.tpl.php b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-wrapper.tpl.php
new file mode 100644
index 0000000..2fa51a7
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator-wrapper.tpl.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Default theme implementation to wrap aggregator content.
+ *
+ * Available variables:
+ * - $content: All aggregator content.
+ * - $page: Pager links rendered through theme_pager().
+ *
+ * @see template_preprocess()
+ * @see template_preprocess_aggregator_wrapper()
+ *
+ * @ingroup themeable
+ */
+?>
+<div id="aggregator">
+ <?php print $content; ?>
+ <?php print $pager; ?>
+</div>
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.admin.inc b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.admin.inc
new file mode 100644
index 0000000..443facb
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.admin.inc
@@ -0,0 +1,633 @@
+<?php
+
+/**
+ * @file
+ * Administration page callbacks for the Aggregator module.
+ */
+
+/**
+ * Page callback: Displays the Aggregator module administration page.
+ */
+function aggregator_admin_overview() {
+ return aggregator_view();
+}
+
+/**
+ * Displays the aggregator administration page.
+ *
+ * @return
+ * A HTML-formatted string with administration page content.
+ */
+function aggregator_view() {
+ $result = db_query('SELECT f.fid, f.title, f.url, f.refresh, f.checked, f.link, f.description, f.hash, f.etag, f.modified, f.image, f.block, 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');
+
+ $output = '<h3>' . t('Feed overview') . '</h3>';
+
+ $header = array(t('Title'), t('Items'), t('Last update'), t('Next update'), array('data' => t('Operations'), 'colspan' => '3'));
+ $rows = array();
+ foreach ($result as $feed) {
+ $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/config/services/aggregator/edit/feed/$feed->fid"),
+ l(t('remove items'), "admin/config/services/aggregator/remove/$feed->fid"),
+ l(t('update items'), "admin/config/services/aggregator/update/$feed->fid", array('query' => array('token' => drupal_get_token("aggregator/update/$feed->fid")))),
+ );
+ }
+ $output .= theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No feeds available. <a href="@link">Add feed</a>.', array('@link' => url('admin/config/services/aggregator/add/feed')))));
+
+ $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');
+
+ $output .= '<h3>' . t('Category overview') . '</h3>';
+
+ $header = array(t('Title'), t('Items'), t('Operations'));
+ $rows = array();
+ foreach ($result as $category) {
+ $rows[] = array(l($category->title, "aggregator/categories/$category->cid"), format_plural($category->items, '1 item', '@count items'), l(t('edit'), "admin/config/services/aggregator/edit/category/$category->cid"));
+ }
+ $output .= theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No categories available. <a href="@link">Add category</a>.', array('@link' => url('admin/config/services/aggregator/add/category')))));
+
+ return $output;
+}
+
+/**
+ * Form constructor for adding and editing feed sources.
+ *
+ * @param $feed
+ * (optional) If editing a feed, the feed to edit as a PHP stdClass value; if
+ * adding a new feed, NULL. Defaults to NULL.
+ *
+ * @ingroup forms
+ * @see aggregator_form_feed_validate()
+ * @see aggregator_form_feed_submit()
+ */
+function aggregator_form_feed($form, &$form_state, stdClass $feed = NULL) {
+ $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
+ $period[AGGREGATOR_CLEAR_NEVER] = t('Never');
+
+ $form['title'] = array('#type' => 'textfield',
+ '#title' => t('Title'),
+ '#default_value' => isset($feed->title) ? $feed->title : '',
+ '#maxlength' => 255,
+ '#description' => t('The name of the feed (or the name of the website providing the feed).'),
+ '#required' => TRUE,
+ );
+ $form['url'] = array('#type' => 'textfield',
+ '#title' => t('URL'),
+ '#default_value' => isset($feed->url) ? $feed->url : '',
+ '#maxlength' => NULL,
+ '#description' => t('The fully-qualified URL of the feed.'),
+ '#required' => TRUE,
+ );
+ $form['refresh'] = array('#type' => 'select',
+ '#title' => t('Update interval'),
+ '#default_value' => isset($feed->refresh) ? $feed->refresh : 3600,
+ '#options' => $period,
+ '#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'))),
+ );
+ $form['block'] = array('#type' => 'select',
+ '#title' => t('News items in block'),
+ '#default_value' => isset($feed->block) ? $feed->block : 5,
+ '#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)),
+ '#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'))),
+ );
+
+ // Handling of categories.
+ $options = array();
+ $values = array();
+ $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));
+ foreach ($categories as $category) {
+ $options[$category->cid] = check_plain($category->title);
+ if ($category->fid) $values[] = $category->cid;
+ }
+
+ if ($options) {
+ $form['category'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Categorize news items'),
+ '#default_value' => $values,
+ '#options' => $options,
+ '#description' => t('New feed items are automatically filed in the checked categories.'),
+ );
+ }
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+ if (!empty($feed->fid)) {
+ $form['actions']['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete'),
+ );
+ $form['fid'] = array(
+ '#type' => 'hidden',
+ '#value' => $feed->fid,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Form validation handler for aggregator_form_feed().
+ *
+ * @see aggregator_form_feed_submit()
+ */
+function aggregator_form_feed_validate($form, &$form_state) {
+ if ($form_state['values']['op'] == t('Save')) {
+ // Ensure URL is valid.
+ if (!valid_url($form_state['values']['url'], TRUE)) {
+ form_set_error('url', t('The URL %url is invalid. Enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $form_state['values']['url'])));
+ }
+ // Check for duplicate titles.
+ if (isset($form_state['values']['fid'])) {
+ $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']));
+ }
+ else {
+ $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']));
+ }
+ foreach ($result as $feed) {
+ if (strcasecmp($feed->title, $form_state['values']['title']) == 0) {
+ form_set_error('title', t('A feed named %feed already exists. Enter a unique title.', array('%feed' => $form_state['values']['title'])));
+ }
+ if (strcasecmp($feed->url, $form_state['values']['url']) == 0) {
+ form_set_error('url', t('A feed with this URL %url already exists. Enter a unique URL.', array('%url' => $form_state['values']['url'])));
+ }
+ }
+ }
+}
+
+/**
+ * Form submission handler for aggregator_form_feed().
+ *
+ * @see aggregator_form_feed_validate()
+ *
+ * @todo Add delete confirmation dialog.
+ */
+function aggregator_form_feed_submit($form, &$form_state) {
+ if ($form_state['values']['op'] == t('Delete')) {
+ $title = $form_state['values']['title'];
+ // Unset the title.
+ unset($form_state['values']['title']);
+ }
+ aggregator_save_feed($form_state['values']);
+ if (isset($form_state['values']['fid'])) {
+ if (isset($form_state['values']['title'])) {
+ drupal_set_message(t('The feed %feed has been updated.', array('%feed' => $form_state['values']['title'])));
+ if (arg(0) == 'admin') {
+ $form_state['redirect'] = 'admin/config/services/aggregator/';
+ return;
+ }
+ else {
+ $form_state['redirect'] = 'aggregator/sources/' . $form_state['values']['fid'];
+ return;
+ }
+ }
+ else {
+ watchdog('aggregator', 'Feed %feed deleted.', array('%feed' => $title));
+ drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => $title)));
+ if (arg(0) == 'admin') {
+ $form_state['redirect'] = 'admin/config/services/aggregator/';
+ return;
+ }
+ else {
+ $form_state['redirect'] = 'aggregator/sources/';
+ return;
+ }
+ }
+ }
+ else {
+ watchdog('aggregator', 'Feed %feed added.', array('%feed' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/config/services/aggregator'));
+ drupal_set_message(t('The feed %feed has been added.', array('%feed' => $form_state['values']['title'])));
+ }
+}
+
+/**
+ * Deletes a feed.
+ *
+ * @param $feed
+ * An associative array describing the feed to be cleared.
+ *
+ * @see aggregator_admin_remove_feed_submit()
+ */
+function aggregator_admin_remove_feed($form, $form_state, $feed) {
+ return confirm_form(
+ array(
+ 'feed' => array(
+ '#type' => 'value',
+ '#value' => $feed,
+ ),
+ ),
+ t('Are you sure you want to remove all items from the feed %feed?', array('%feed' => $feed->title)),
+ 'admin/config/services/aggregator',
+ t('This action cannot be undone.'),
+ t('Remove items'),
+ t('Cancel')
+ );
+}
+
+/**
+ * Form submission handler for aggregator_admin_remove_feed().
+ *
+ * Removes all items from a feed and redirects to the overview page.
+ */
+function aggregator_admin_remove_feed_submit($form, &$form_state) {
+ aggregator_remove($form_state['values']['feed']);
+ $form_state['redirect'] = 'admin/config/services/aggregator';
+}
+
+/**
+ * Form constructor for importing feeds from OPML.
+ *
+ * @ingroup forms
+ * @see aggregator_form_opml_validate()
+ * @see aggregator_form_opml_submit()
+ */
+function aggregator_form_opml($form, &$form_state) {
+ $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
+
+ $form['upload'] = array(
+ '#type' => 'file',
+ '#title' => t('OPML File'),
+ '#description' => t('Upload an OPML file containing a list of feeds to be imported.'),
+ );
+ $form['remote'] = array(
+ '#type' => 'textfield',
+ '#title' => t('OPML Remote URL'),
+ '#maxlength' => 1024,
+ '#description' => t('Enter the URL of an OPML file. This file will be downloaded and processed only once on submission of the form.'),
+ );
+ $form['refresh'] = array(
+ '#type' => 'select',
+ '#title' => t('Update interval'),
+ '#default_value' => 3600,
+ '#options' => $period,
+ '#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'))),
+ );
+ $form['block'] = array('#type' => 'select',
+ '#title' => t('News items in block'),
+ '#default_value' => 5,
+ '#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)),
+ '#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'))),
+ );
+
+ // Handling of categories.
+ $options = array_map('check_plain', db_query("SELECT cid, title FROM {aggregator_category} ORDER BY title")->fetchAllKeyed());
+ if ($options) {
+ $form['category'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Categorize news items'),
+ '#options' => $options,
+ '#description' => t('New feed items are automatically filed in the checked categories.'),
+ );
+ }
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Import')
+ );
+
+ return $form;
+}
+
+/**
+ * Form validation handler for aggregator_form_opml().
+ *
+ * @see aggregator_form_opml_submit()
+ */
+function aggregator_form_opml_validate($form, &$form_state) {
+ // If both fields are empty or filled, cancel.
+ if (empty($form_state['values']['remote']) == empty($_FILES['files']['name']['upload'])) {
+ form_set_error('remote', t('You must <em>either</em> upload a file or enter a URL.'));
+ }
+
+ // Validate the URL, if one was entered.
+ if (!empty($form_state['values']['remote']) && !valid_url($form_state['values']['remote'], TRUE)) {
+ form_set_error('remote', t('This URL is not valid.'));
+ }
+}
+
+/**
+ * Form submission handler for aggregator_form_opml().
+ *
+ * @see aggregator_form_opml_validate()
+ */
+function aggregator_form_opml_submit($form, &$form_state) {
+ $data = '';
+ $validators = array('file_validate_extensions' => array('opml xml'));
+ if ($file = file_save_upload('upload', $validators)) {
+ $data = file_get_contents($file->uri);
+ }
+ else {
+ $response = drupal_http_request($form_state['values']['remote']);
+ if (!isset($response->error)) {
+ $data = $response->data;
+ }
+ }
+
+ $feeds = _aggregator_parse_opml($data);
+ if (empty($feeds)) {
+ drupal_set_message(t('No new feed has been added.'));
+ return;
+ }
+
+ $form_state['values']['op'] = t('Save');
+
+ foreach ($feeds as $feed) {
+ // Ensure URL is valid.
+ if (!valid_url($feed['url'], TRUE)) {
+ drupal_set_message(t('The URL %url is invalid.', array('%url' => $feed['url'])), 'warning');
+ continue;
+ }
+
+ // Check for duplicate titles or URLs.
+ $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = :title OR url = :url", array(':title' => $feed['title'], ':url' => $feed['url']));
+ foreach ($result as $old) {
+ if (strcasecmp($old->title, $feed['title']) == 0) {
+ drupal_set_message(t('A feed named %title already exists.', array('%title' => $old->title)), 'warning');
+ continue 2;
+ }
+ if (strcasecmp($old->url, $feed['url']) == 0) {
+ drupal_set_message(t('A feed with the URL %url already exists.', array('%url' => $old->url)), 'warning');
+ continue 2;
+ }
+ }
+
+ $form_state['values']['title'] = $feed['title'];
+ $form_state['values']['url'] = $feed['url'];
+ drupal_form_submit('aggregator_form_feed', $form_state);
+ }
+
+ $form_state['redirect'] = 'admin/config/services/aggregator';
+}
+
+/**
+ * Parses an OPML file.
+ *
+ * Feeds are recognized as <outline> elements with the attributes "text" and
+ * "xmlurl" set.
+ *
+ * @param $opml
+ * The complete contents of an OPML document.
+ *
+ * @return
+ * An array of feeds, each an associative array with a "title" and a "url"
+ * element, or NULL if the OPML document failed to be parsed. An empty array
+ * will be returned if the document is valid but contains no feeds, as some
+ * OPML documents do.
+ */
+function _aggregator_parse_opml($opml) {
+ $feeds = array();
+ $xml_parser = drupal_xml_parser_create($opml);
+ if (xml_parse_into_struct($xml_parser, $opml, $values)) {
+ foreach ($values as $entry) {
+ if ($entry['tag'] == 'OUTLINE' && isset($entry['attributes'])) {
+ $item = $entry['attributes'];
+ if (!empty($item['XMLURL']) && !empty($item['TEXT'])) {
+ $feeds[] = array('title' => $item['TEXT'], 'url' => $item['XMLURL']);
+ }
+ }
+ }
+ }
+ xml_parser_free($xml_parser);
+
+ return $feeds;
+}
+
+/**
+ * Page callback: Refreshes a feed, then redirects to the overview page.
+ *
+ * @param $feed
+ * An object describing the feed to be refreshed.
+ */
+function aggregator_admin_refresh_feed($feed) {
+ if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'aggregator/update/' . $feed->fid)) {
+ return MENU_ACCESS_DENIED;
+ }
+ aggregator_refresh($feed);
+ drupal_goto('admin/config/services/aggregator');
+}
+
+/**
+ * Form constructor for the aggregator system settings.
+ *
+ * @see aggregator_admin_form_submit()
+ * @ingroup forms
+ */
+function aggregator_admin_form($form, $form_state) {
+ // Global aggregator settings.
+ $form['aggregator_allowed_html_tags'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Allowed HTML tags'),
+ '#size' => 80,
+ '#maxlength' => 255,
+ '#default_value' => variable_get('aggregator_allowed_html_tags', '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'),
+ '#description' => t('A space-separated list of HTML tags allowed in the content of feed items. Disallowed tags are stripped from the content.'),
+ );
+
+ // Make sure configuration is sane.
+ aggregator_sanitize_configuration();
+
+ // Get all available fetchers.
+ $fetchers = module_implements('aggregator_fetch');
+ foreach ($fetchers as $k => $module) {
+ if ($info = module_invoke($module, 'aggregator_fetch_info')) {
+ $label = $info['title'] . ' <span class="description">' . $info['description'] . '</span>';
+ }
+ else {
+ $label = $module;
+ }
+ unset($fetchers[$k]);
+ $fetchers[$module] = $label;
+ }
+
+ // Get all available parsers.
+ $parsers = module_implements('aggregator_parse');
+ foreach ($parsers as $k => $module) {
+ if ($info = module_invoke($module, 'aggregator_parse_info')) {
+ $label = $info['title'] . ' <span class="description">' . $info['description'] . '</span>';
+ }
+ else {
+ $label = $module;
+ }
+ unset($parsers[$k]);
+ $parsers[$module] = $label;
+ }
+
+ // Get all available processors.
+ $processors = module_implements('aggregator_process');
+ foreach ($processors as $k => $module) {
+ if ($info = module_invoke($module, 'aggregator_process_info')) {
+ $label = $info['title'] . ' <span class="description">' . $info['description'] . '</span>';
+ }
+ else {
+ $label = $module;
+ }
+ unset($processors[$k]);
+ $processors[$module] = $label;
+ }
+
+ // Only show basic configuration if there are actually options.
+ $basic_conf = array();
+ if (count($fetchers) > 1) {
+ $basic_conf['aggregator_fetcher'] = array(
+ '#type' => 'radios',
+ '#title' => t('Fetcher'),
+ '#description' => t('Fetchers download data from an external source. Choose a fetcher suitable for the external source you would like to download from.'),
+ '#options' => $fetchers,
+ '#default_value' => variable_get('aggregator_fetcher', 'aggregator'),
+ );
+ }
+ if (count($parsers) > 1) {
+ $basic_conf['aggregator_parser'] = array(
+ '#type' => 'radios',
+ '#title' => t('Parser'),
+ '#description' => t('Parsers transform downloaded data into standard structures. Choose a parser suitable for the type of feeds you would like to aggregate.'),
+ '#options' => $parsers,
+ '#default_value' => variable_get('aggregator_parser', 'aggregator'),
+ );
+ }
+ if (count($processors) > 1) {
+ $basic_conf['aggregator_processors'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Processors'),
+ '#description' => t('Processors act on parsed feed data, for example they store feed items. Choose the processors suitable for your task.'),
+ '#options' => $processors,
+ '#default_value' => variable_get('aggregator_processors', array('aggregator')),
+ );
+ }
+ if (count($basic_conf)) {
+ $form['basic_conf'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Basic configuration'),
+ '#description' => t('For most aggregation tasks, the default settings are fine.'),
+ '#collapsible' => TRUE,
+ '#collapsed' => FALSE,
+ );
+ $form['basic_conf'] += $basic_conf;
+ }
+
+ // Implementing modules will expect an array at $form['modules'].
+ $form['modules'] = array();
+
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save configuration'),
+ );
+
+ return $form;
+}
+
+/**
+ * Form submission handler for aggregator_admin_form().
+ */
+function aggregator_admin_form_submit($form, &$form_state) {
+ if (isset($form_state['values']['aggregator_processors'])) {
+ $form_state['values']['aggregator_processors'] = array_filter($form_state['values']['aggregator_processors']);
+ }
+ system_settings_form_submit($form, $form_state);
+}
+
+/**
+ * Form constructor to add/edit/delete aggregator categories.
+ *
+ * @param $edit
+ * An associative array containing:
+ * - title: A string to use for the category title.
+ * - description: A string to use for the category description.
+ * - cid: The category ID.
+ *
+ * @ingroup forms
+ * @see aggregator_form_category_validate()
+ * @see aggregator_form_category_submit()
+ */
+function aggregator_form_category($form, &$form_state, $edit = array('title' => '', 'description' => '', 'cid' => NULL)) {
+ $form['title'] = array('#type' => 'textfield',
+ '#title' => t('Title'),
+ '#default_value' => $edit['title'],
+ '#maxlength' => 64,
+ '#required' => TRUE,
+ );
+ $form['description'] = array('#type' => 'textarea',
+ '#title' => t('Description'),
+ '#default_value' => $edit['description'],
+ );
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+ if ($edit['cid']) {
+ $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+ $form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']);
+ }
+
+ return $form;
+}
+
+/**
+ * Form validation handler for aggregator_form_category().
+ *
+ * @see aggregator_form_category_submit()
+ */
+function aggregator_form_category_validate($form, &$form_state) {
+ if ($form_state['values']['op'] == t('Save')) {
+ // Check for duplicate titles
+ if (isset($form_state['values']['cid'])) {
+ $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();
+ }
+ else {
+ $category = db_query("SELECT cid FROM {aggregator_category} WHERE title = :title", array(':title' => $form_state['values']['title']))->fetchObject();
+ }
+ if ($category) {
+ form_set_error('title', t('A category named %category already exists. Enter a unique title.', array('%category' => $form_state['values']['title'])));
+ }
+ }
+}
+
+/**
+ * Form submission handler for aggregator_form_category().
+ *
+ * @see aggregator_form_category_validate()
+ *
+ * @todo Add delete confirmation dialog.
+ */
+function aggregator_form_category_submit($form, &$form_state) {
+ if ($form_state['values']['op'] == t('Delete')) {
+ $title = $form_state['values']['title'];
+ // Unset the title.
+ unset($form_state['values']['title']);
+ }
+ aggregator_save_category($form_state['values']);
+ if (isset($form_state['values']['cid'])) {
+ if (isset($form_state['values']['title'])) {
+ drupal_set_message(t('The category %category has been updated.', array('%category' => $form_state['values']['title'])));
+ if (arg(0) == 'admin') {
+ $form_state['redirect'] = 'admin/config/services/aggregator/';
+ return;
+ }
+ else {
+ $form_state['redirect'] = 'aggregator/categories/' . $form_state['values']['cid'];
+ return;
+ }
+ }
+ else {
+ watchdog('aggregator', 'Category %category deleted.', array('%category' => $title));
+ drupal_set_message(t('The category %category has been deleted.', array('%category' => $title)));
+ if (arg(0) == 'admin') {
+ $form_state['redirect'] = 'admin/config/services/aggregator/';
+ return;
+ }
+ else {
+ $form_state['redirect'] = 'aggregator/categories/';
+ return;
+ }
+ }
+ }
+ else {
+ watchdog('aggregator', 'Category %category added.', array('%category' => $form_state['values']['title']), WATCHDOG_NOTICE, l(t('view'), 'admin/config/services/aggregator'));
+ drupal_set_message(t('The category %category has been added.', array('%category' => $form_state['values']['title'])));
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.api.php b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.api.php
new file mode 100644
index 0000000..d5cac4e
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.api.php
@@ -0,0 +1,220 @@
+<?php
+
+/**
+ * @file
+ * Documentation for aggregator API.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Create an alternative fetcher for aggregator.module.
+ *
+ * A fetcher downloads feed data to a Drupal site. The fetcher is called at the
+ * first of the three aggregation stages: first, data is downloaded by the
+ * active fetcher; second, it is converted to a common format by the active
+ * parser; and finally, it is passed to all active processors, which manipulate
+ * or store the data.
+ *
+ * Modules that define this hook can be set as the active fetcher within the
+ * configuration page. Only one fetcher can be active at a time.
+ *
+ * @param $feed
+ * A feed object representing the resource to be downloaded. $feed->url
+ * contains the link to the feed. Download the data at the URL and expose it
+ * to other modules by attaching it to $feed->source_string.
+ *
+ * @return
+ * TRUE if fetching was successful, FALSE otherwise.
+ *
+ * @see hook_aggregator_fetch_info()
+ * @see hook_aggregator_parse()
+ * @see hook_aggregator_process()
+ *
+ * @ingroup aggregator
+ */
+function hook_aggregator_fetch($feed) {
+ $feed->source_string = mymodule_fetch($feed->url);
+}
+
+/**
+ * Specify the title and short description of your fetcher.
+ *
+ * The title and the description provided are shown within the configuration
+ * page. Use as title the human readable name of the fetcher and as description
+ * a brief (40 to 80 characters) explanation of the fetcher's functionality.
+ *
+ * This hook is only called if your module implements hook_aggregator_fetch().
+ * If this hook is not implemented aggregator will use your module's file name
+ * as title and there will be no description.
+ *
+ * @return
+ * An associative array defining a title and a description string.
+ *
+ * @see hook_aggregator_fetch()
+ *
+ * @ingroup aggregator
+ */
+function hook_aggregator_fetch_info() {
+ return array(
+ 'title' => t('Default fetcher'),
+ 'description' => t('Default fetcher for resources available by URL.'),
+ );
+}
+
+/**
+ * Create an alternative parser for aggregator module.
+ *
+ * A parser converts feed item data to a common format. The parser is called
+ * at the second of the three aggregation stages: first, data is downloaded
+ * by the active fetcher; second, it is converted to a common format by the
+ * active parser; and finally, it is passed to all active processors which
+ * manipulate or store the data.
+ *
+ * Modules that define this hook can be set as the active parser within the
+ * configuration page. Only one parser can be active at a time.
+ *
+ * @param $feed
+ * An object describing the resource to be parsed. $feed->source_string
+ * contains the raw feed data. The hook implementation should parse this data
+ * and add the following properties to the $feed object:
+ * - description: The human-readable description of the feed.
+ * - link: A full URL that directly relates to the feed.
+ * - image: An image URL used to display an image of the feed.
+ * - etag: An entity tag from the HTTP header used for cache validation to
+ * determine if the content has been changed.
+ * - modified: The UNIX timestamp when the feed was last modified.
+ * - items: An array of feed items. The common format for a single feed item
+ * is an associative array containing:
+ * - title: The human-readable title of the feed item.
+ * - description: The full body text of the item or a summary.
+ * - timestamp: The UNIX timestamp when the feed item was last published.
+ * - author: The author of the feed item.
+ * - guid: The global unique identifier (GUID) string that uniquely
+ * identifies the item. If not available, the link is used to identify
+ * the item.
+ * - link: A full URL to the individual feed item.
+ *
+ * @return
+ * TRUE if parsing was successful, FALSE otherwise.
+ *
+ * @see hook_aggregator_parse_info()
+ * @see hook_aggregator_fetch()
+ * @see hook_aggregator_process()
+ *
+ * @ingroup aggregator
+ */
+function hook_aggregator_parse($feed) {
+ if ($items = mymodule_parse($feed->source_string)) {
+ $feed->items = $items;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Specify the title and short description of your parser.
+ *
+ * The title and the description provided are shown within the configuration
+ * page. Use as title the human readable name of the parser and as description
+ * a brief (40 to 80 characters) explanation of the parser's functionality.
+ *
+ * This hook is only called if your module implements hook_aggregator_parse().
+ * If this hook is not implemented aggregator will use your module's file name
+ * as title and there will be no description.
+ *
+ * @return
+ * An associative array defining a title and a description string.
+ *
+ * @see hook_aggregator_parse()
+ *
+ * @ingroup aggregator
+ */
+function hook_aggregator_parse_info() {
+ return array(
+ 'title' => t('Default parser'),
+ 'description' => t('Default parser for RSS, Atom and RDF feeds.'),
+ );
+}
+
+/**
+ * Create a processor for aggregator.module.
+ *
+ * A processor acts on parsed feed data. Active processors are called at the
+ * third and last of the aggregation stages: first, data is downloaded by the
+ * active fetcher; second, it is converted to a common format by the active
+ * parser; and finally, it is passed to all active processors that manipulate or
+ * store the data.
+ *
+ * Modules that define this hook can be activated as a processor within the
+ * configuration page.
+ *
+ * @param $feed
+ * A feed object representing the resource to be processed. $feed->items
+ * contains an array of feed items downloaded and parsed at the parsing stage.
+ * See hook_aggregator_parse() for the basic format of a single item in the
+ * $feed->items array. For the exact format refer to the particular parser in
+ * use.
+ *
+ * @see hook_aggregator_process_info()
+ * @see hook_aggregator_fetch()
+ * @see hook_aggregator_parse()
+ *
+ * @ingroup aggregator
+ */
+function hook_aggregator_process($feed) {
+ foreach ($feed->items as $item) {
+ mymodule_save($item);
+ }
+}
+
+/**
+ * Specify the title and short description of your processor.
+ *
+ * The title and the description provided are shown within the configuration
+ * page. Use as title the natural name of the processor and as description a
+ * brief (40 to 80 characters) explanation of the functionality.
+ *
+ * This hook is only called if your module implements hook_aggregator_process().
+ * If this hook is not implemented aggregator will use your module's file name
+ * as title and there will be no description.
+ *
+ * @return
+ * An associative array defining a title and a description string.
+ *
+ * @see hook_aggregator_process()
+ *
+ * @ingroup aggregator
+ */
+function hook_aggregator_process_info() {
+ return array(
+ 'title' => t('Default processor'),
+ 'description' => t('Creates lightweight records of feed items.'),
+ );
+}
+
+/**
+ * Remove stored feed data.
+ *
+ * Aggregator calls this hook if either a feed is deleted or a user clicks on
+ * "remove items".
+ *
+ * If your module stores feed items for example on hook_aggregator_process() it
+ * is recommended to implement this hook and to remove data related to $feed
+ * when called.
+ *
+ * @param $feed
+ * The $feed object whose items are being removed.
+ *
+ * @ingroup aggregator
+ */
+function hook_aggregator_remove($feed) {
+ mymodule_remove_items($feed->fid);
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.css b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.css
new file mode 100644
index 0000000..4285631
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.css
@@ -0,0 +1,41 @@
+/**
+ * Styles for theme in the Aggregator module.
+ */
+
+#aggregator .feed-source .feed-title {
+ margin-top: 0;
+}
+#aggregator .feed-source .feed-image img {
+ margin-bottom: 0.75em;
+}
+#aggregator .feed-source .feed-icon {
+ float: right; /* LTR */
+ display: block;
+}
+#aggregator .feed-item {
+ margin-bottom: 1.5em;
+}
+#aggregator .feed-item-title {
+ margin-bottom: 0;
+ font-size: 1.3em;
+}
+#aggregator .feed-item-meta,
+#aggregator .feed-item-body {
+ margin-bottom: 0.5em;
+}
+#aggregator .feed-item-categories {
+ font-size: 0.9em;
+}
+#aggregator td {
+ vertical-align: bottom;
+}
+#aggregator td.categorize-item {
+ white-space: nowrap;
+}
+#aggregator .categorize-item .news-item .body {
+ margin-top: 0;
+}
+#aggregator .categorize-item h3 {
+ margin-bottom: 1em;
+ margin-top: 0;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.fetcher.inc b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.fetcher.inc
new file mode 100644
index 0000000..0f72877
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.fetcher.inc
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Fetcher functions for the aggregator module.
+ */
+
+/**
+ * Implements hook_aggregator_fetch_info().
+ */
+function aggregator_aggregator_fetch_info() {
+ return array(
+ 'title' => t('Default fetcher'),
+ 'description' => t('Downloads data from a URL using Drupal\'s HTTP request handler.'),
+ );
+}
+
+/**
+ * Implements hook_aggregator_fetch().
+ */
+function aggregator_aggregator_fetch($feed) {
+ $feed->source_string = FALSE;
+
+ // Generate conditional GET headers.
+ $headers = array();
+ if ($feed->etag) {
+ $headers['If-None-Match'] = $feed->etag;
+ }
+ if ($feed->modified) {
+ $headers['If-Modified-Since'] = gmdate(DATE_RFC1123, $feed->modified);
+ }
+
+ // Request feed.
+ $result = drupal_http_request($feed->url, array('headers' => $headers));
+
+ // Process HTTP response code.
+ switch ($result->code) {
+ case 304:
+ break;
+ case 301:
+ $feed->url = $result->redirect_url;
+ // Do not break here.
+ case 200:
+ case 302:
+ case 307:
+ if (!isset($result->data)) {
+ $result->data = '';
+ }
+ if (!isset($result->headers)) {
+ $result->headers = array();
+ }
+ $feed->source_string = $result->data;
+ $feed->http_headers = $result->headers;
+ break;
+ default:
+ watchdog('aggregator', 'The feed from %site seems to be broken, due to "%error".', array('%site' => $feed->title, '%error' => $result->code . ' ' . $result->error), WATCHDOG_WARNING);
+ drupal_set_message(t('The feed from %site seems to be broken, because of error "%error".', array('%site' => $feed->title, '%error' => $result->code . ' ' . $result->error)));
+ }
+
+ return $feed->source_string === FALSE ? FALSE : TRUE;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.info b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.info
new file mode 100644
index 0000000..17fb2e5
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.info
@@ -0,0 +1,14 @@
+name = Aggregator
+description = "Aggregates syndicated content (RSS, RDF, and Atom feeds)."
+package = Core
+version = VERSION
+core = 7.x
+files[] = aggregator.test
+configure = admin/config/services/aggregator/settings
+stylesheets[all][] = aggregator.css
+
+; Information added by Drupal.org packaging script on 2014-01-15
+version = "7.26"
+project = "drupal"
+datestamp = "1389815930"
+
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.install b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.install
new file mode 100644
index 0000000..b84556a
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.install
@@ -0,0 +1,330 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the aggregator module.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function aggregator_uninstall() {
+ variable_del('aggregator_allowed_html_tags');
+ variable_del('aggregator_summary_items');
+ variable_del('aggregator_clear');
+ variable_del('aggregator_category_selector');
+ variable_del('aggregator_fetcher');
+ variable_del('aggregator_parser');
+ variable_del('aggregator_processors');
+ variable_del('aggregator_teaser_length');
+}
+
+/**
+ * Implements hook_schema().
+ */
+function aggregator_schema() {
+ $schema['aggregator_category'] = array(
+ 'description' => 'Stores categories for aggregator feeds and feed items.',
+ 'fields' => array(
+ 'cid' => array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique aggregator category ID.',
+ ),
+ 'title' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Title of the category.',
+ ),
+ 'description' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ 'description' => 'Description of the category',
+ ),
+ 'block' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => 'The number of recent items to show within the category block.',
+ )
+ ),
+ 'primary key' => array('cid'),
+ 'unique keys' => array(
+ 'title' => array('title'),
+ ),
+ );
+
+ $schema['aggregator_category_feed'] = array(
+ 'description' => 'Bridge table; maps feeds to categories.',
+ 'fields' => array(
+ 'fid' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => "The feed's {aggregator_feed}.fid.",
+ ),
+ 'cid' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The {aggregator_category}.cid to which the feed is being assigned.',
+ )
+ ),
+ 'primary key' => array('cid', 'fid'),
+ 'indexes' => array(
+ 'fid' => array('fid'),
+ ),
+ 'foreign keys' => array(
+ 'aggregator_category' => array(
+ 'table' => 'aggregator_category',
+ 'columns' => array('cid' => 'cid'),
+ ),
+ ),
+ );
+
+ $schema['aggregator_category_item'] = array(
+ 'description' => 'Bridge table; maps feed items to categories.',
+ 'fields' => array(
+ 'iid' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => "The feed item's {aggregator_item}.iid.",
+ ),
+ 'cid' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The {aggregator_category}.cid to which the feed item is being assigned.',
+ )
+ ),
+ 'primary key' => array('cid', 'iid'),
+ 'indexes' => array(
+ 'iid' => array('iid'),
+ ),
+ 'foreign keys' => array(
+ 'aggregator_category' => array(
+ 'table' => 'aggregator_category',
+ 'columns' => array('cid' => 'cid'),
+ ),
+ ),
+ );
+
+ $schema['aggregator_feed'] = array(
+ 'description' => 'Stores feeds to be parsed by the aggregator.',
+ 'fields' => array(
+ 'fid' => array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique feed ID.',
+ ),
+ 'title' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Title of the feed.',
+ ),
+ 'url' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'description' => 'URL to the feed.',
+ ),
+ 'refresh' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'How often to check for new feed items, in seconds.',
+ ),
+ 'checked' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Last time feed was checked for new items, as Unix timestamp.',
+ ),
+ 'queued' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Time when this feed was queued for refresh, 0 if not queued.',
+ ),
+ 'link' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'description' => 'The parent website of the feed; comes from the <link> element in the feed.',
+ ),
+ 'description' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ 'description' => "The parent website's description; comes from the <description> element in the feed.",
+ ),
+ 'image' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ 'description' => 'An image representing the feed.',
+ ),
+ 'hash' => array(
+ 'type' => 'varchar',
+ 'length' => 64,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Calculated hash of the feed data, used for validating cache.',
+ ),
+ 'etag' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Entity tag HTTP response header, used for validating cache.',
+ ),
+ 'modified' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'When the feed was last modified, as a Unix timestamp.',
+ ),
+ 'block' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ 'description' => "Number of items to display in the feed's block.",
+ )
+ ),
+ 'primary key' => array('fid'),
+ 'indexes' => array(
+ 'url' => array(array('url', 255)),
+ 'queued' => array('queued'),
+ ),
+ 'unique keys' => array(
+ 'title' => array('title'),
+ ),
+ );
+
+ $schema['aggregator_item'] = array(
+ 'description' => 'Stores the individual items imported from feeds.',
+ 'fields' => array(
+ 'iid' => array(
+ 'type' => 'serial',
+ 'not null' => TRUE,
+ 'description' => 'Primary Key: Unique ID for feed item.',
+ ),
+ 'fid' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The {aggregator_feed}.fid to which this item belongs.',
+ ),
+ 'title' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Title of the feed item.',
+ ),
+ 'link' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'description' => 'Link to the feed item.',
+ ),
+ 'author' => array(
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ 'description' => 'Author of the feed item.',
+ ),
+ 'description' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'size' => 'big',
+ 'description' => 'Body of the feed item.',
+ ),
+ 'timestamp' => array(
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'description' => 'Posted date of the feed item, as a Unix timestamp.',
+ ),
+ 'guid' => array(
+ 'type' => 'text',
+ 'not null' => TRUE,
+ 'description' => 'Unique identifier for the feed item.',
+ )
+ ),
+ 'primary key' => array('iid'),
+ 'indexes' => array(
+ 'fid' => array('fid'),
+ ),
+ 'foreign keys' => array(
+ 'aggregator_feed' => array(
+ 'table' => 'aggregator_feed',
+ 'columns' => array('fid' => 'fid'),
+ ),
+ ),
+ );
+
+ return $schema;
+}
+
+/**
+ * @addtogroup updates-6.x-to-7.x
+ * @{
+ */
+
+/**
+ * Add hash column to aggregator_feed table.
+ */
+function aggregator_update_7000() {
+ db_add_field('aggregator_feed', 'hash', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => ''));
+}
+
+/**
+ * Add aggregator teaser length to settings from old global default teaser length
+ */
+function aggregator_update_7001() {
+ variable_set('aggregator_teaser_length', variable_get('teaser_length'));
+}
+
+/**
+ * Add queued timestamp.
+ */
+function aggregator_update_7002() {
+ db_add_field('aggregator_feed', 'queued', array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Time when this feed was queued for refresh, 0 if not queued.',
+ ));
+ db_add_index('aggregator_feed', 'queued', array('queued'));
+}
+
+/**
+ * @} End of "addtogroup updates-6.x-to-7.x"
+ */
+
+/**
+ * @addtogroup updates-7.x-extra
+ * @{
+ */
+
+/**
+ * Increase the length of {aggregator_feed}.url.
+ */
+function aggregator_update_7003() {
+ db_drop_unique_key('aggregator_feed', 'url');
+ db_change_field('aggregator_feed', 'url', 'url', array('type' => 'text', 'not null' => TRUE, 'description' => 'URL to the feed.'));
+ db_change_field('aggregator_feed', 'link', 'link', array('type' => 'text', 'not null' => TRUE, 'description' => 'The parent website of the feed; comes from the <link> element in the feed.'));
+ db_change_field('aggregator_item', 'link', 'link', array('type' => 'text', 'not null' => TRUE, 'description' => 'Link to the feed item.'));
+ db_change_field('aggregator_item', 'guid', 'guid', array('type' => 'text', 'not null' => TRUE, 'description' => 'Unique identifier for the feed item.'));
+ db_add_index('aggregator_feed', 'url', array(array('url', 255)));
+}
+
+/**
+ * @} End of "addtogroup updates-7.x-extra"
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.module b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.module
new file mode 100644
index 0000000..70f8c5c
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.module
@@ -0,0 +1,782 @@
+<?php
+
+/**
+ * @file
+ * Used to aggregate syndicated content (RSS, RDF, and Atom).
+ */
+
+/**
+ * Denotes that a feed's items should never expire.
+ */
+define('AGGREGATOR_CLEAR_NEVER', 0);
+
+/**
+ * Implements hook_help().
+ */
+function aggregator_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#aggregator':
+ $output = '';
+ $output .= '<h3>' . t('About') . '</h3>';
+ $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>';
+ $output .= '<h3>' . t('Uses') . '</h3>';
+ $output .= '<dl>';
+ $output .= '<dt>' . t('Viewing feeds') . '</dt>';
+ $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>';
+ $output .= '<dt>' . t('Adding, editing, and deleting feeds') . '</dt>';
+ $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>';
+ $output .= '<dt>' . t('OPML integration') . '</dt>';
+ $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>';
+ $output .= '<dt>' . t('Configuring cron') . '</dt>';
+ $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>';
+ $output .= '</dl>';
+ return $output;
+ case 'admin/config/services/aggregator':
+ $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>';
+ $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>';
+ return $output;
+ case 'admin/config/services/aggregator/add/feed':
+ return '<p>' . t('Add a feed in RSS, RDF or Atom format. A feed may only have one entry.') . '</p>';
+ case 'admin/config/services/aggregator/add/category':
+ 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>';
+ case 'admin/config/services/aggregator/add/opml':
+ 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>';
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function aggregator_theme() {
+ return array(
+ 'aggregator_wrapper' => array(
+ 'variables' => array('content' => NULL),
+ 'file' => 'aggregator.pages.inc',
+ 'template' => 'aggregator-wrapper',
+ ),
+ 'aggregator_categorize_items' => array(
+ 'render element' => 'form',
+ 'file' => 'aggregator.pages.inc',
+ ),
+ 'aggregator_feed_source' => array(
+ 'variables' => array('feed' => NULL),
+ 'file' => 'aggregator.pages.inc',
+ 'template' => 'aggregator-feed-source',
+ ),
+ 'aggregator_block_item' => array(
+ 'variables' => array('item' => NULL, 'feed' => 0),
+ ),
+ 'aggregator_summary_items' => array(
+ 'variables' => array('summary_items' => NULL, 'source' => NULL),
+ 'file' => 'aggregator.pages.inc',
+ 'template' => 'aggregator-summary-items',
+ ),
+ 'aggregator_summary_item' => array(
+ 'variables' => array('item' => NULL),
+ 'file' => 'aggregator.pages.inc',
+ 'template' => 'aggregator-summary-item',
+ ),
+ 'aggregator_item' => array(
+ 'variables' => array('item' => NULL),
+ 'file' => 'aggregator.pages.inc',
+ 'template' => 'aggregator-item',
+ ),
+ 'aggregator_page_opml' => array(
+ 'variables' => array('feeds' => NULL),
+ 'file' => 'aggregator.pages.inc',
+ ),
+ 'aggregator_page_rss' => array(
+ 'variables' => array('feeds' => NULL, 'category' => NULL),
+ 'file' => 'aggregator.pages.inc',
+ ),
+ );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function aggregator_menu() {
+ $items['admin/config/services/aggregator'] = array(
+ 'title' => 'Feed aggregator',
+ 'description' => "Configure which content your site aggregates from other sites, how often it polls them, and how they're categorized.",
+ 'page callback' => 'aggregator_admin_overview',
+ 'access arguments' => array('administer news feeds'),
+ 'weight' => 10,
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['admin/config/services/aggregator/add/feed'] = array(
+ 'title' => 'Add feed',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_form_feed'),
+ 'access arguments' => array('administer news feeds'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['admin/config/services/aggregator/add/category'] = array(
+ 'title' => 'Add category',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_form_category'),
+ 'access arguments' => array('administer news feeds'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['admin/config/services/aggregator/add/opml'] = array(
+ 'title' => 'Import OPML',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_form_opml'),
+ 'access arguments' => array('administer news feeds'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['admin/config/services/aggregator/remove/%aggregator_feed'] = array(
+ 'title' => 'Remove items',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_admin_remove_feed', 5),
+ 'access arguments' => array('administer news feeds'),
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['admin/config/services/aggregator/update/%aggregator_feed'] = array(
+ 'title' => 'Update items',
+ 'page callback' => 'aggregator_admin_refresh_feed',
+ 'page arguments' => array(5),
+ 'access arguments' => array('administer news feeds'),
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['admin/config/services/aggregator/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['admin/config/services/aggregator/settings'] = array(
+ 'title' => 'Settings',
+ 'description' => 'Configure the behavior of the feed aggregator, including when to discard feed items and how to present feed items and categories.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_admin_form'),
+ 'access arguments' => array('administer news feeds'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['aggregator'] = array(
+ 'title' => 'Feed aggregator',
+ 'page callback' => 'aggregator_page_last',
+ 'access arguments' => array('access news feeds'),
+ 'weight' => 5,
+ 'file' => 'aggregator.pages.inc',
+ );
+ $items['aggregator/sources'] = array(
+ 'title' => 'Sources',
+ 'page callback' => 'aggregator_page_sources',
+ 'access arguments' => array('access news feeds'),
+ 'file' => 'aggregator.pages.inc',
+ );
+ $items['aggregator/categories'] = array(
+ 'title' => 'Categories',
+ 'page callback' => 'aggregator_page_categories',
+ 'access callback' => '_aggregator_has_categories',
+ 'file' => 'aggregator.pages.inc',
+ );
+ $items['aggregator/rss'] = array(
+ 'title' => 'RSS feed',
+ 'page callback' => 'aggregator_page_rss',
+ 'access arguments' => array('access news feeds'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'aggregator.pages.inc',
+ );
+ $items['aggregator/opml'] = array(
+ 'title' => 'OPML feed',
+ 'page callback' => 'aggregator_page_opml',
+ 'access arguments' => array('access news feeds'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'aggregator.pages.inc',
+ );
+ $items['aggregator/categories/%aggregator_category'] = array(
+ 'title callback' => '_aggregator_category_title',
+ 'title arguments' => array(2),
+ 'page callback' => 'aggregator_page_category',
+ 'page arguments' => array(2),
+ 'access arguments' => array('access news feeds'),
+ 'file' => 'aggregator.pages.inc',
+ );
+ $items['aggregator/categories/%aggregator_category/view'] = array(
+ 'title' => 'View',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['aggregator/categories/%aggregator_category/categorize'] = array(
+ 'title' => 'Categorize',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_page_category_form', 2),
+ 'access arguments' => array('administer news feeds'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'aggregator.pages.inc',
+ );
+ $items['aggregator/categories/%aggregator_category/configure'] = array(
+ 'title' => 'Configure',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_form_category', 2),
+ 'access arguments' => array('administer news feeds'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 1,
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['aggregator/sources/%aggregator_feed'] = array(
+ 'page callback' => 'aggregator_page_source',
+ 'page arguments' => array(2),
+ 'access arguments' => array('access news feeds'),
+ 'file' => 'aggregator.pages.inc',
+ );
+ $items['aggregator/sources/%aggregator_feed/view'] = array(
+ 'title' => 'View',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['aggregator/sources/%aggregator_feed/categorize'] = array(
+ 'title' => 'Categorize',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_page_source_form', 2),
+ 'access arguments' => array('administer news feeds'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'aggregator.pages.inc',
+ );
+ $items['aggregator/sources/%aggregator_feed/configure'] = array(
+ 'title' => 'Configure',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_form_feed', 2),
+ 'access arguments' => array('administer news feeds'),
+ 'type' => MENU_LOCAL_TASK,
+ 'weight' => 1,
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['admin/config/services/aggregator/edit/feed/%aggregator_feed'] = array(
+ 'title' => 'Edit feed',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_form_feed', 6),
+ 'access arguments' => array('administer news feeds'),
+ 'file' => 'aggregator.admin.inc',
+ );
+ $items['admin/config/services/aggregator/edit/category/%aggregator_category'] = array(
+ 'title' => 'Edit category',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('aggregator_form_category', 6),
+ 'access arguments' => array('administer news feeds'),
+ 'file' => 'aggregator.admin.inc',
+ );
+
+ return $items;
+}
+
+/**
+ * Title callback: Returns a title for aggregator category pages.
+ *
+ * @param $category
+ * An aggregator category.
+ *
+ * @return
+ * A string with the aggregator category title.
+ */
+function _aggregator_category_title($category) {
+ return $category['title'];
+}
+
+/**
+ * Determines whether there are any aggregator categories.
+ *
+ * @return
+ * TRUE if there is at least one category and the user has access to them;
+ * FALSE otherwise.
+ */
+function _aggregator_has_categories() {
+ return user_access('access news feeds') && (bool) db_query_range('SELECT 1 FROM {aggregator_category}', 0, 1)->fetchField();
+}
+
+/**
+ * Implements hook_permission().
+ */
+function aggregator_permission() {
+ return array(
+ 'administer news feeds' => array(
+ 'title' => t('Administer news feeds'),
+ ),
+ 'access news feeds' => array(
+ 'title' => t('View news feeds'),
+ ),
+ );
+}
+
+/**
+ * Implements hook_cron().
+ *
+ * Queues news feeds for updates once their refresh interval has elapsed.
+ */
+function aggregator_cron() {
+ $result = db_query('SELECT * FROM {aggregator_feed} WHERE queued = 0 AND checked + refresh < :time AND refresh <> :never', array(
+ ':time' => REQUEST_TIME,
+ ':never' => AGGREGATOR_CLEAR_NEVER
+ ));
+ $queue = DrupalQueue::get('aggregator_feeds');
+ foreach ($result as $feed) {
+ if ($queue->createItem($feed)) {
+ // Add timestamp to avoid queueing item more than once.
+ db_update('aggregator_feed')
+ ->fields(array('queued' => REQUEST_TIME))
+ ->condition('fid', $feed->fid)
+ ->execute();
+ }
+ }
+
+ // Remove queued timestamp after 6 hours assuming the update has failed.
+ db_update('aggregator_feed')
+ ->fields(array('queued' => 0))
+ ->condition('queued', REQUEST_TIME - (3600 * 6), '<')
+ ->execute();
+}
+
+/**
+ * Implements hook_cron_queue_info().
+ */
+function aggregator_cron_queue_info() {
+ $queues['aggregator_feeds'] = array(
+ 'worker callback' => 'aggregator_refresh',
+ 'time' => 60,
+ );
+ return $queues;
+}
+
+/**
+ * Implements hook_block_info().
+ */
+function aggregator_block_info() {
+ $blocks = array();
+ $result = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title');
+ foreach ($result as $category) {
+ $blocks['category-' . $category->cid]['info'] = t('!title category latest items', array('!title' => $category->title));
+ }
+ $result = db_query('SELECT fid, title FROM {aggregator_feed} WHERE block <> 0 ORDER BY fid');
+ foreach ($result as $feed) {
+ $blocks['feed-' . $feed->fid]['info'] = t('!title feed latest items', array('!title' => $feed->title));
+ }
+ return $blocks;
+}
+
+/**
+ * Implements hook_block_configure().
+ */
+function aggregator_block_configure($delta = '') {
+ list($type, $id) = explode('-', $delta);
+ if ($type == 'category') {
+ $value = db_query('SELECT block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchField();
+ $form['block'] = array(
+ '#type' => 'select',
+ '#title' => t('Number of news items in block'),
+ '#default_value' => $value,
+ '#options' => drupal_map_assoc(range(2, 20)),
+ );
+ return $form;
+ }
+}
+
+/**
+ * Implements hook_block_save().
+ */
+function aggregator_block_save($delta = '', $edit = array()) {
+ list($type, $id) = explode('-', $delta);
+ if ($type == 'category') {
+ db_update('aggregator_category')
+ ->fields(array('block' => $edit['block']))
+ ->condition('cid', $id)
+ ->execute();
+ }
+}
+
+/**
+ * Implements hook_block_view().
+ *
+ * Generates blocks for the latest news items in each category and feed.
+ */
+function aggregator_block_view($delta = '') {
+ if (user_access('access news feeds')) {
+ $block = array();
+ list($type, $id) = explode('-', $delta);
+ $result = FALSE;
+ switch ($type) {
+ case 'feed':
+ if ($feed = db_query('SELECT fid, title, block FROM {aggregator_feed} WHERE block <> 0 AND fid = :fid', array(':fid' => $id))->fetchObject()) {
+ $block['subject'] = check_plain($feed->title);
+ $result = db_query_range("SELECT * FROM {aggregator_item} WHERE fid = :fid ORDER BY timestamp DESC, iid DESC", 0, $feed->block, array(':fid' => $id));
+ $read_more = theme('more_link', array('url' => 'aggregator/sources/' . $feed->fid, 'title' => t("View this feed's recent news.")));
+ }
+ break;
+
+ case 'category':
+ if ($category = db_query('SELECT cid, title, block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchObject()) {
+ $block['subject'] = check_plain($category->title);
+ $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));
+ $read_more = theme('more_link', array('url' => 'aggregator/categories/' . $category->cid, 'title' => t("View this category's recent news.")));
+ }
+ break;
+ }
+
+ $items = array();
+ if (!empty($result)) {
+ foreach ($result as $item) {
+ $items[] = theme('aggregator_block_item', array('item' => $item));
+ }
+ }
+
+ // Only display the block if there are items to show.
+ if (count($items) > 0) {
+ $block['content'] = theme('item_list', array('items' => $items)) . $read_more;
+ }
+ return $block;
+ }
+}
+
+/**
+ * Adds/edits/deletes aggregator categories.
+ *
+ * @param $edit
+ * An associative array describing the category to be added/edited/deleted.
+ */
+function aggregator_save_category($edit) {
+ $link_path = 'aggregator/categories/';
+ if (!empty($edit['cid'])) {
+ $link_path .= $edit['cid'];
+ if (!empty($edit['title'])) {
+ db_merge('aggregator_category')
+ ->key(array('cid' => $edit['cid']))
+ ->fields(array(
+ 'title' => $edit['title'],
+ 'description' => $edit['description'],
+ ))
+ ->execute();
+ $op = 'update';
+ }
+ else {
+ db_delete('aggregator_category')
+ ->condition('cid', $edit['cid'])
+ ->execute();
+ // Make sure there is no active block for this category.
+ if (module_exists('block')) {
+ db_delete('block')
+ ->condition('module', 'aggregator')
+ ->condition('delta', 'category-' . $edit['cid'])
+ ->execute();
+ }
+ $edit['title'] = '';
+ $op = 'delete';
+ }
+ }
+ elseif (!empty($edit['title'])) {
+ // A single unique id for bundles and feeds, to use in blocks.
+ $link_path .= db_insert('aggregator_category')
+ ->fields(array(
+ 'title' => $edit['title'],
+ 'description' => $edit['description'],
+ 'block' => 5,
+ ))
+ ->execute();
+ $op = 'insert';
+ }
+ if (isset($op)) {
+ menu_link_maintain('aggregator', $op, $link_path, $edit['title']);
+ }
+}
+
+/**
+ * Add/edit/delete an aggregator feed.
+ *
+ * @param $edit
+ * An associative array describing the feed to be added/edited/deleted.
+ */
+function aggregator_save_feed($edit) {
+ if (!empty($edit['fid'])) {
+ // An existing feed is being modified, delete the category listings.
+ db_delete('aggregator_category_feed')
+ ->condition('fid', $edit['fid'])
+ ->execute();
+ }
+ if (!empty($edit['fid']) && !empty($edit['title'])) {
+ db_update('aggregator_feed')
+ ->condition('fid', $edit['fid'])
+ ->fields(array(
+ 'title' => $edit['title'],
+ 'url' => $edit['url'],
+ 'refresh' => $edit['refresh'],
+ 'block' => $edit['block'],
+ ))
+ ->execute();
+ }
+ elseif (!empty($edit['fid'])) {
+ $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $edit['fid']))->fetchCol();
+ if ($iids) {
+ db_delete('aggregator_category_item')
+ ->condition('iid', $iids, 'IN')
+ ->execute();
+ }
+ db_delete('aggregator_feed')->
+ condition('fid', $edit['fid'])
+ ->execute();
+ db_delete('aggregator_item')
+ ->condition('fid', $edit['fid'])
+ ->execute();
+ // Make sure there is no active block for this feed.
+ if (module_exists('block')) {
+ db_delete('block')
+ ->condition('module', 'aggregator')
+ ->condition('delta', 'feed-' . $edit['fid'])
+ ->execute();
+ }
+ }
+ elseif (!empty($edit['title'])) {
+ $edit['fid'] = db_insert('aggregator_feed')
+ ->fields(array(
+ 'title' => $edit['title'],
+ 'url' => $edit['url'],
+ 'refresh' => $edit['refresh'],
+ 'block' => $edit['block'],
+ 'link' => '',
+ 'description' => '',
+ 'image' => '',
+ ))
+ ->execute();
+
+ }
+ if (!empty($edit['title'])) {
+ // The feed is being saved, save the categories as well.
+ if (!empty($edit['category'])) {
+ foreach ($edit['category'] as $cid => $value) {
+ if ($value) {
+ db_insert('aggregator_category_feed')
+ ->fields(array(
+ 'fid' => $edit['fid'],
+ 'cid' => $cid,
+ ))
+ ->execute();
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Removes all items from a feed.
+ *
+ * @param $feed
+ * An object describing the feed to be cleared.
+ */
+function aggregator_remove($feed) {
+ _aggregator_get_variables();
+ // Call hook_aggregator_remove() on all modules.
+ module_invoke_all('aggregator_remove', $feed);
+ // Reset feed.
+ db_update('aggregator_feed')
+ ->condition('fid', $feed->fid)
+ ->fields(array(
+ 'checked' => 0,
+ 'hash' => '',
+ 'etag' => '',
+ 'modified' => 0,
+ ))
+ ->execute();
+}
+
+/**
+ * Gets the fetcher, parser, and processors.
+ *
+ * @return
+ * An array containing the fetcher, parser, and processors.
+ */
+function _aggregator_get_variables() {
+ // Fetch the feed.
+ $fetcher = variable_get('aggregator_fetcher', 'aggregator');
+ if ($fetcher == 'aggregator') {
+ include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator.fetcher.inc';
+ }
+ $parser = variable_get('aggregator_parser', 'aggregator');
+ if ($parser == 'aggregator') {
+ include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator.parser.inc';
+ }
+ $processors = variable_get('aggregator_processors', array('aggregator'));
+ if (in_array('aggregator', $processors)) {
+ include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/aggregator.processor.inc';
+ }
+ return array($fetcher, $parser, $processors);
+}
+
+/**
+ * Checks a news feed for new items.
+ *
+ * @param $feed
+ * An object describing the feed to be refreshed.
+ */
+function aggregator_refresh($feed) {
+ // Store feed URL to track changes.
+ $feed_url = $feed->url;
+
+ // Fetch the feed.
+ list($fetcher, $parser, $processors) = _aggregator_get_variables();
+ $success = module_invoke($fetcher, 'aggregator_fetch', $feed);
+
+ // We store the hash of feed data in the database. When refreshing a
+ // feed we compare stored hash and new hash calculated from downloaded
+ // data. If both are equal we say that feed is not updated.
+ $hash = hash('sha256', $feed->source_string);
+
+ if ($success && ($feed->hash != $hash)) {
+ // Parse the feed.
+ if (module_invoke($parser, 'aggregator_parse', $feed)) {
+ // Update feed with parsed data.
+ db_merge('aggregator_feed')
+ ->key(array('fid' => $feed->fid))
+ ->fields(array(
+ 'url' => $feed->url,
+ 'link' => empty($feed->link) ? $feed->url : $feed->link,
+ 'description' => empty($feed->description) ? '' : $feed->description,
+ 'image' => empty($feed->image) ? '' : $feed->image,
+ 'hash' => $hash,
+ 'etag' => empty($feed->etag) ? '' : $feed->etag,
+ 'modified' => empty($feed->modified) ? 0 : $feed->modified,
+ ))
+ ->execute();
+
+ // Log if feed URL has changed.
+ if ($feed->url != $feed_url) {
+ watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed->title, '%url' => $feed->url));
+ }
+
+ watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed->title));
+ drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed->title)));
+
+ // If there are items on the feed, let all enabled processors do their work on it.
+ if (@count($feed->items)) {
+ foreach ($processors as $processor) {
+ module_invoke($processor, 'aggregator_process', $feed);
+ }
+ }
+ }
+ }
+ else {
+ drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed->title)));
+ }
+
+ // Regardless of successful or not, indicate that this feed has been checked.
+ db_update('aggregator_feed')
+ ->fields(array('checked' => REQUEST_TIME, 'queued' => 0))
+ ->condition('fid', $feed->fid)
+ ->execute();
+
+ // Expire old feed items.
+ if (function_exists('aggregator_expire')) {
+ aggregator_expire($feed);
+ }
+}
+
+/**
+ * Loads an aggregator feed.
+ *
+ * @param $fid
+ * The feed id.
+ *
+ * @return
+ * An object describing the feed.
+ */
+function aggregator_feed_load($fid) {
+ $feeds = &drupal_static(__FUNCTION__);
+ if (!isset($feeds[$fid])) {
+ $feeds[$fid] = db_query('SELECT * FROM {aggregator_feed} WHERE fid = :fid', array(':fid' => $fid))->fetchObject();
+ }
+
+ return $feeds[$fid];
+}
+
+/**
+ * Loads an aggregator category.
+ *
+ * @param $cid
+ * The category id.
+ *
+ * @return
+ * An associative array describing the category.
+ */
+function aggregator_category_load($cid) {
+ $categories = &drupal_static(__FUNCTION__);
+ if (!isset($categories[$cid])) {
+ $categories[$cid] = db_query('SELECT * FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $cid))->fetchAssoc();
+ }
+
+ return $categories[$cid];
+}
+
+/**
+ * Returns HTML for an individual feed item for display in the block.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - item: The item to be displayed.
+ * - feed: Not used.
+ *
+ * @ingroup themeable
+ */
+function theme_aggregator_block_item($variables) {
+ // Display the external link to the item.
+ return '<a href="' . check_url($variables['item']->link) . '">' . check_plain($variables['item']->title) . "</a>\n";
+}
+
+/**
+ * Renders the HTML content safely, as allowed.
+ *
+ * @param $value
+ * The content to be filtered.
+ *
+ * @return
+ * The filtered content.
+ */
+function aggregator_filter_xss($value) {
+ 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));
+}
+
+/**
+ * Checks and sanitizes the aggregator configuration.
+ *
+ * Goes through all fetchers, parsers and processors and checks whether they
+ * are available. If one is missing, resets to standard configuration.
+ *
+ * @return
+ * TRUE if this function resets the configuration; FALSE if not.
+ */
+function aggregator_sanitize_configuration() {
+ $reset = FALSE;
+ list($fetcher, $parser, $processors) = _aggregator_get_variables();
+ if (!module_exists($fetcher)) {
+ $reset = TRUE;
+ }
+ if (!module_exists($parser)) {
+ $reset = TRUE;
+ }
+ foreach ($processors as $processor) {
+ if (!module_exists($processor)) {
+ $reset = TRUE;
+ break;
+ }
+ }
+ if ($reset) {
+ variable_del('aggregator_fetcher');
+ variable_del('aggregator_parser');
+ variable_del('aggregator_processors');
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Helper function for drupal_map_assoc.
+ *
+ * @param $count
+ * Items count.
+ *
+ * @return
+ * A string that is plural-formatted as "@count items".
+ */
+function _aggregator_items($count) {
+ return format_plural($count, '1 item', '@count items');
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.pages.inc b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.pages.inc
new file mode 100644
index 0000000..bfba3ff
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.pages.inc
@@ -0,0 +1,582 @@
+<?php
+
+/**
+ * @file
+ * User page callbacks for the Aggregator module.
+ */
+
+/**
+ * Page callback: Displays the most recent items gathered from any feed.
+ *
+ * @return
+ * The rendered list of items for the feed.
+ */
+function aggregator_page_last() {
+ drupal_add_feed('aggregator/rss', variable_get('site_name', 'Drupal') . ' ' . t('aggregator'));
+
+ $items = aggregator_feed_items_load('sum');
+
+ return _aggregator_page_list($items, arg(1));
+}
+
+/**
+ * Page callback: Displays all the items captured from the particular feed.
+ *
+ * @param $feed
+ * The feed for which to display all items.
+ *
+ * @return
+ * The rendered list of items for a feed.
+ *
+ * @see aggregator_menu()
+ */
+function aggregator_page_source($feed) {
+ drupal_set_title($feed->title);
+ $feed_source = theme('aggregator_feed_source', array('feed' => $feed));
+
+ // It is safe to include the fid in the query because it's loaded from the
+ // database by aggregator_feed_load.
+ $items = aggregator_feed_items_load('source', $feed);
+
+ return _aggregator_page_list($items, arg(3), $feed_source);
+}
+
+/**
+ * Page callback: Displays a form with all items captured from a feed.
+ *
+ * @param $feed
+ * The feed for which to list all of the aggregated items.
+ *
+ * @return
+ * The rendered list of items for the feed.
+ *
+ * @see aggregator_page_source()
+ */
+function aggregator_page_source_form($form, $form_state, $feed) {
+ return aggregator_page_source($feed);
+}
+
+/**
+ * Page callback: Displays all the items aggregated in a particular category.
+ *
+ * @param $category
+ * The category for which to list all of the aggregated items.
+ *
+ * @return
+ * The rendered list of items for the feed.
+ */
+function aggregator_page_category($category) {
+ drupal_add_feed('aggregator/rss/' . $category['cid'], variable_get('site_name', 'Drupal') . ' ' . t('aggregator - @title', array('@title' => $category['title'])));
+
+ // It is safe to include the cid in the query because it's loaded from the
+ // database by aggregator_category_load.
+ $items = aggregator_feed_items_load('category', $category);
+
+ return _aggregator_page_list($items, arg(3));
+}
+
+/**
+ * Page callback: Displays a form containing items aggregated in a category.
+ *
+ * @param $category
+ * The category for which to list all of the aggregated items.
+ *
+ * @return
+ * The rendered list of items for the feed.
+ *
+ * @see aggregator_page_category()
+ */
+function aggregator_page_category_form($form, $form_state, $category) {
+ return aggregator_page_category($category);
+}
+
+/**
+ * Loads and optionally filters feed items.
+ *
+ * @param $type
+ * The type of filter for the items. Possible values are:
+ * - sum: No filtering.
+ * - source: Filter the feed items, limiting the result to items from a
+ * single source.
+ * - category: Filter the feed items by category.
+ * @param $data
+ * Feed or category data used for filtering. The type and value of $data
+ * depends on $type:
+ * - source: $data is an object with $data->fid identifying the feed used to
+ * as filter.
+ * - category: $data is an array with $data['cid'] being the category id to
+ * filter on.
+ * The $data parameter is not used when $type is 'sum'.
+ *
+ * @return
+ * An array of the feed items.
+ */
+function aggregator_feed_items_load($type, $data = NULL) {
+ $items = array();
+ switch ($type) {
+ case 'sum':
+ $query = db_select('aggregator_item', 'i');
+ $query->join('aggregator_feed', 'f', 'i.fid = f.fid');
+ $query->fields('i');
+ $query->addField('f', 'title', 'ftitle');
+ $query->addField('f', 'link', 'flink');
+ break;
+ case 'source':
+ $query = db_select('aggregator_item', 'i');
+ $query
+ ->fields('i')
+ ->condition('i.fid', $data->fid);
+ break;
+ case 'category':
+ $query = db_select('aggregator_category_item', 'c');
+ $query->leftJoin('aggregator_item', 'i', 'c.iid = i.iid');
+ $query->leftJoin('aggregator_feed', 'f', 'i.fid = f.fid');
+ $query
+ ->fields('i')
+ ->condition('cid', $data['cid']);
+ $query->addField('f', 'title', 'ftitle');
+ $query->addField('f', 'link', 'flink');
+ break;
+ }
+
+ $result = $query
+ ->extend('PagerDefault')
+ ->limit(20)
+ ->orderBy('i.timestamp', 'DESC')
+ ->orderBy('i.iid', 'DESC')
+ ->execute();
+
+ foreach ($result as $item) {
+ $item->categories = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = :iid ORDER BY c.title', array(':iid' => $item->iid))->fetchAll();
+ $items[] = $item;
+ }
+
+ return $items;
+}
+
+/**
+ * Prints an aggregator page listing a number of feed items.
+ *
+ * Various menu callbacks use this function to print their feeds.
+ *
+ * @param $items
+ * The items to be listed.
+ * @param $op
+ * Which form should be added to the items. Only 'categorize' is now
+ * recognized.
+ * @param $feed_source
+ * The feed source URL.
+ *
+ * @return
+ * The rendered list of items for the feed.
+ */
+function _aggregator_page_list($items, $op, $feed_source = '') {
+ if (user_access('administer news feeds') && ($op == 'categorize')) {
+ // Get form data.
+ $output = aggregator_categorize_items($items, $feed_source);
+ }
+ else {
+ // Assemble themed output.
+ $output = $feed_source;
+ foreach ($items as $item) {
+ $output .= theme('aggregator_item', array('item' => $item));
+ }
+ $output = theme('aggregator_wrapper', array('content' => $output));
+ }
+
+ return $output;
+}
+
+/**
+ * Form constructor to build the page list form.
+ *
+ * @param $items
+ * An array of the feed items.
+ * @param $feed_source
+ * (optional) The feed source URL. Defaults to an empty string.
+ *
+ * @return array
+ * An array of FAPI elements.
+ *
+ * @see aggregator_categorize_items_submit()
+ * @see theme_aggregator_categorize_items()
+ * @ingroup forms
+ */
+function aggregator_categorize_items($items, $feed_source = '') {
+ $form['#submit'][] = 'aggregator_categorize_items_submit';
+ $form['#theme'] = 'aggregator_categorize_items';
+ $form['feed_source'] = array(
+ '#value' => $feed_source,
+ );
+ $categories = array();
+ $done = FALSE;
+ $form['items'] = array();
+ $form['categories'] = array(
+ '#tree' => TRUE,
+ );
+ foreach ($items as $item) {
+ $form['items'][$item->iid] = array('#markup' => theme('aggregator_item', array('item' => $item)));
+ $form['categories'][$item->iid] = array();
+ $categories_result = db_query('SELECT c.cid, c.title, ci.iid FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid AND ci.iid = :iid', array(':iid' => $item->iid));
+ $selected = array();
+ foreach ($categories_result as $category) {
+ if (!$done) {
+ $categories[$category->cid] = check_plain($category->title);
+ }
+ if ($category->iid) {
+ $selected[] = $category->cid;
+ }
+ }
+ $done = TRUE;
+ $form['categories'][$item->iid] = array(
+ '#type' => variable_get('aggregator_category_selector', 'checkboxes'),
+ '#default_value' => $selected,
+ '#options' => $categories,
+ '#size' => 10,
+ '#multiple' => TRUE
+ );
+ }
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save categories'));
+
+ return $form;
+}
+
+/**
+ * Form submission handler for aggregator_categorize_items().
+ */
+function aggregator_categorize_items_submit($form, &$form_state) {
+ if (!empty($form_state['values']['categories'])) {
+ foreach ($form_state['values']['categories'] as $iid => $selection) {
+ db_delete('aggregator_category_item')
+ ->condition('iid', $iid)
+ ->execute();
+ $insert = db_insert('aggregator_category_item')->fields(array('iid', 'cid'));
+ $has_values = FALSE;
+ foreach ($selection as $cid) {
+ if ($cid && $iid) {
+ $has_values = TRUE;
+ $insert->values(array(
+ 'iid' => $iid,
+ 'cid' => $cid,
+ ));
+ }
+ }
+ if ($has_values) {
+ $insert->execute();
+ }
+ }
+ }
+ drupal_set_message(t('The categories have been saved.'));
+}
+
+/**
+ * Returns HTML for the aggregator page list form for assigning categories.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_aggregator_categorize_items($variables) {
+ $form = $variables['form'];
+
+ $output = drupal_render($form['feed_source']);
+ $rows = array();
+ if (!empty($form['items'])) {
+ foreach (element_children($form['items']) as $key) {
+ $rows[] = array(
+ drupal_render($form['items'][$key]),
+ array('data' => drupal_render($form['categories'][$key]), 'class' => array('categorize-item')),
+ );
+ }
+ }
+ $output .= theme('table', array('header' => array('', t('Categorize')), 'rows' => $rows));
+ $output .= drupal_render($form['submit']);
+ $output .= drupal_render_children($form);
+
+ return theme('aggregator_wrapper', array('content' => $output));
+}
+
+/**
+ * Processes variables for aggregator-wrapper.tpl.php.
+ *
+ * @see aggregator-wrapper.tpl.php
+ */
+function template_preprocess_aggregator_wrapper(&$variables) {
+ $variables['pager'] = theme('pager');
+}
+
+/**
+ * Processes variables for aggregator-item.tpl.php.
+ *
+ * @see aggregator-item.tpl.php
+ */
+function template_preprocess_aggregator_item(&$variables) {
+ $item = $variables['item'];
+
+ $variables['feed_url'] = check_url($item->link);
+ $variables['feed_title'] = check_plain($item->title);
+ $variables['content'] = aggregator_filter_xss($item->description);
+
+ $variables['source_url'] = '';
+ $variables['source_title'] = '';
+ if (isset($item->ftitle) && isset($item->fid)) {
+ $variables['source_url'] = url("aggregator/sources/$item->fid");
+ $variables['source_title'] = check_plain($item->ftitle);
+ }
+ if (date('Ymd', $item->timestamp) == date('Ymd')) {
+ $variables['source_date'] = t('%ago ago', array('%ago' => format_interval(REQUEST_TIME - $item->timestamp)));
+ }
+ else {
+ $variables['source_date'] = format_date($item->timestamp, 'custom', variable_get('date_format_medium', 'D, m/d/Y - H:i'));
+ }
+
+ $variables['categories'] = array();
+ foreach ($item->categories as $category) {
+ $variables['categories'][$category->cid] = l($category->title, 'aggregator/categories/' . $category->cid);
+ }
+}
+
+/**
+ * Page callback: Displays all the feeds used by the aggregator.
+ *
+ * @return
+ * An HTML-formatted string.
+ *
+ * @see aggregator_menu()
+ */
+function aggregator_page_sources() {
+ $result = db_query('SELECT f.fid, f.title, f.description, f.image, MAX(i.timestamp) AS last FROM {aggregator_feed} f LEFT JOIN {aggregator_item} i ON f.fid = i.fid GROUP BY f.fid, f.title, f.description, f.image ORDER BY last DESC, f.title');
+
+ $output = '';
+ foreach ($result as $feed) {
+ // Most recent items:
+ $summary_items = array();
+ if (variable_get('aggregator_summary_items', 3)) {
+ $items = db_query_range('SELECT i.title, i.timestamp, i.link FROM {aggregator_item} i WHERE i.fid = :fid ORDER BY i.timestamp DESC', 0, variable_get('aggregator_summary_items', 3), array(':fid' => $feed->fid));
+ foreach ($items as $item) {
+ $summary_items[] = theme('aggregator_summary_item', array('item' => $item));
+ }
+ }
+ $feed->url = url('aggregator/sources/' . $feed->fid);
+ $output .= theme('aggregator_summary_items', array('summary_items' => $summary_items, 'source' => $feed));
+ }
+ $output .= theme('feed_icon', array('url' => 'aggregator/opml', 'title' => t('OPML feed')));
+
+ return theme('aggregator_wrapper', array('content' => $output));
+}
+
+/**
+ * Page callback: Displays all the categories used by the Aggregator module.
+ *
+ * @return string
+ * An HTML formatted string.
+ *
+ * @see aggregator_menu()
+ */
+function aggregator_page_categories() {
+ $result = db_query('SELECT c.cid, c.title, c.description FROM {aggregator_category} c LEFT JOIN {aggregator_category_item} ci ON c.cid = ci.cid LEFT JOIN {aggregator_item} i ON ci.iid = i.iid GROUP BY c.cid, c.title, c.description');
+
+ $output = '';
+ foreach ($result as $category) {
+ if (variable_get('aggregator_summary_items', 3)) {
+ $summary_items = array();
+ $items = db_query_range('SELECT i.title, i.timestamp, i.link, f.title as feed_title, f.link as feed_link FROM {aggregator_category_item} ci LEFT JOIN {aggregator_item} i ON i.iid = ci.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE ci.cid = :cid ORDER BY i.timestamp DESC', 0, variable_get('aggregator_summary_items', 3), array(':cid' => $category->cid));
+ foreach ($items as $item) {
+ $summary_items[] = theme('aggregator_summary_item', array('item' => $item));
+ }
+ }
+ $category->url = url('aggregator/categories/' . $category->cid);
+ $output .= theme('aggregator_summary_items', array('summary_items' => $summary_items, 'source' => $category));
+ }
+
+ return theme('aggregator_wrapper', array('content' => $output));
+}
+
+/**
+ * Page callback: Generates an RSS 0.92 feed of aggregator items or categories.
+ *
+ * @return string
+ * An HTML formatted string.
+ */
+function aggregator_page_rss() {
+ $result = NULL;
+ // arg(2) is the passed cid, only select for that category.
+ if (arg(2)) {
+ $category = db_query('SELECT cid, title FROM {aggregator_category} WHERE cid = :cid', array(':cid' => arg(2)))->fetchObject();
+ $result = db_query_range('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_category_item} c LEFT JOIN {aggregator_item} i ON c.iid = i.iid LEFT JOIN {aggregator_feed} f ON i.fid = f.fid WHERE cid = :cid ORDER BY timestamp DESC, i.iid DESC', 0, variable_get('feed_default_items', 10), array(':cid' => $category->cid));
+ }
+ // Or, get the default aggregator items.
+ else {
+ $category = NULL;
+ $result = db_query_range('SELECT i.*, f.title AS ftitle, f.link AS flink FROM {aggregator_item} i INNER JOIN {aggregator_feed} f ON i.fid = f.fid ORDER BY i.timestamp DESC, i.iid DESC', 0, variable_get('feed_default_items', 10));
+ }
+
+ $feeds = $result->fetchAll();
+ return theme('aggregator_page_rss', array('feeds' => $feeds, 'category' => $category));
+}
+
+/**
+ * Prints the RSS page for a feed.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - feeds: An array of the feeds to theme.
+ * - category: A common category, if any, for all the feeds.
+ *
+ * @return void
+ *
+ * @ingroup themeable
+ */
+function theme_aggregator_page_rss($variables) {
+ $feeds = $variables['feeds'];
+ $category = $variables['category'];
+
+ drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8');
+
+ $items = '';
+ $feed_length = variable_get('feed_item_length', 'fulltext');
+ foreach ($feeds as $feed) {
+ switch ($feed_length) {
+ case 'teaser':
+ $summary = text_summary($feed->description, NULL, variable_get('aggregator_teaser_length', 600));
+ if ($summary != $feed->description) {
+ $summary .= '<p><a href="' . check_url($feed->link) . '">' . t('read more') . "</a></p>\n";
+ }
+ $feed->description = $summary;
+ break;
+ case 'title':
+ $feed->description = '';
+ break;
+ }
+ $items .= format_rss_item($feed->ftitle . ': ' . $feed->title, $feed->link, $feed->description, array('pubDate' => date('r', $feed->timestamp)));
+ }
+
+ $site_name = variable_get('site_name', 'Drupal');
+ $url = url((isset($category) ? 'aggregator/categories/' . $category->cid : 'aggregator'), array('absolute' => TRUE));
+ $description = isset($category) ? t('@site_name - aggregated feeds in category @title', array('@site_name' => $site_name, '@title' => $category->title)) : t('@site_name - aggregated feeds', array('@site_name' => $site_name));
+
+ $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+ $output .= "<rss version=\"2.0\">\n";
+ $output .= format_rss_channel(t('@site_name aggregator', array('@site_name' => $site_name)), $url, $description, $items);
+ $output .= "</rss>\n";
+
+ print $output;
+}
+
+/**
+ * Page callback: Generates an OPML representation of all feeds.
+ *
+ * @param $cid
+ * (optional) If set, feeds are exported only from a category with this ID.
+ * Otherwise, all feeds are exported. Defaults to NULL.
+ *
+ * @return
+ * An OPML formatted string.
+ */
+function aggregator_page_opml($cid = NULL) {
+ if ($cid) {
+ $result = db_query('SELECT f.title, f.url FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} c on f.fid = c.fid WHERE c.cid = :cid ORDER BY title', array(':cid' => $cid));
+ }
+ else {
+ $result = db_query('SELECT * FROM {aggregator_feed} ORDER BY title');
+ }
+
+ $feeds = $result->fetchAll();
+ return theme('aggregator_page_opml', array('feeds' => $feeds));
+}
+
+/**
+ * Prints the OPML page for the feed.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - feeds: An array of the feeds to theme.
+ *
+ * @ingroup themeable
+ */
+function theme_aggregator_page_opml($variables) {
+ $feeds = $variables['feeds'];
+
+ drupal_add_http_header('Content-Type', 'text/xml; charset=utf-8');
+
+ $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
+ $output .= "<opml version=\"1.1\">\n";
+ $output .= "<head>\n";
+ $output .= '<title>' . check_plain(variable_get('site_name', 'Drupal')) . "</title>\n";
+ $output .= '<dateModified>' . gmdate(DATE_RFC2822, REQUEST_TIME) . "</dateModified>\n";
+ $output .= "</head>\n";
+ $output .= "<body>\n";
+ foreach ($feeds as $feed) {
+ $output .= '<outline text="' . check_plain($feed->title) . '" xmlUrl="' . check_url($feed->url) . "\" />\n";
+ }
+ $output .= "</body>\n";
+ $output .= "</opml>\n";
+
+ print $output;
+}
+
+/**
+ * Processes variables for aggregator-summary-items.tpl.php.
+ *
+ * @see aggregator-summary-items.tpl.php
+ */
+function template_preprocess_aggregator_summary_items(&$variables) {
+ $variables['title'] = check_plain($variables['source']->title);
+ $variables['summary_list'] = theme('item_list', array('items' => $variables['summary_items']));
+ $variables['source_url'] = $variables['source']->url;
+}
+
+/**
+ * Processes variables for aggregator-summary-item.tpl.php.
+ *
+ * @see aggregator-summary-item.tpl.php
+ */
+function template_preprocess_aggregator_summary_item(&$variables) {
+ $item = $variables['item'];
+
+ $variables['feed_url'] = check_url($item->link);
+ $variables['feed_title'] = check_plain($item->title);
+ $variables['feed_age'] = t('%age old', array('%age' => format_interval(REQUEST_TIME - $item->timestamp)));
+
+ $variables['source_url'] = '';
+ $variables['source_title'] = '';
+ if (!empty($item->feed_link)) {
+ $variables['source_url'] = check_url($item->feed_link);
+ $variables['source_title'] = check_plain($item->feed_title);
+ }
+}
+
+/**
+ * Processes variables for aggregator-feed-source.tpl.php.
+ *
+ * @see aggregator-feed-source.tpl.php
+ */
+function template_preprocess_aggregator_feed_source(&$variables) {
+ $feed = $variables['feed'];
+
+ $variables['source_icon'] = theme('feed_icon', array('url' => $feed->url, 'title' => t('!title feed', array('!title' => $feed->title))));
+
+ if (!empty($feed->image) && !empty($feed->title) && !empty($feed->link)) {
+ $variables['source_image'] = l(theme('image', array('path' => $feed->image, 'alt' => $feed->title)), $feed->link, array('html' => TRUE, 'attributes' => array('class' => 'feed-image')));
+ }
+ else {
+ $variables['source_image'] = '';
+ }
+
+ $variables['source_description'] = aggregator_filter_xss($feed->description);
+ $variables['source_url'] = check_url(url($feed->link, array('absolute' => TRUE)));
+
+ if ($feed->checked) {
+ $variables['last_checked'] = t('@time ago', array('@time' => format_interval(REQUEST_TIME - $feed->checked)));
+ }
+ else {
+ $variables['last_checked'] = t('never');
+ }
+
+ if (user_access('administer news feeds')) {
+ $variables['last_checked'] = l($variables['last_checked'], 'admin/config/services/aggregator');
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.parser.inc b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.parser.inc
new file mode 100644
index 0000000..91fbe3a
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.parser.inc
@@ -0,0 +1,329 @@
+<?php
+
+/**
+ * @file
+ * Parser functions for the aggregator module.
+ */
+
+/**
+ * Implements hook_aggregator_parse_info().
+ */
+function aggregator_aggregator_parse_info() {
+ return array(
+ 'title' => t('Default parser'),
+ 'description' => t('Parses RSS, Atom and RDF feeds.'),
+ );
+}
+
+/**
+ * Implements hook_aggregator_parse().
+ */
+function aggregator_aggregator_parse($feed) {
+ global $channel, $image;
+
+ // Filter the input data.
+ if (aggregator_parse_feed($feed->source_string, $feed)) {
+ $modified = empty($feed->http_headers['last-modified']) ? 0 : strtotime($feed->http_headers['last-modified']);
+
+ // Prepare the channel data.
+ foreach ($channel as $key => $value) {
+ $channel[$key] = trim($value);
+ }
+
+ // Prepare the image data (if any).
+ foreach ($image as $key => $value) {
+ $image[$key] = trim($value);
+ }
+
+ $etag = empty($feed->http_headers['etag']) ? '' : $feed->http_headers['etag'];
+
+ // Add parsed data to the feed object.
+ $feed->link = !empty($channel['link']) ? $channel['link'] : '';
+ $feed->description = !empty($channel['description']) ? $channel['description'] : '';
+ $feed->image = !empty($image['url']) ? $image['url'] : '';
+ $feed->etag = $etag;
+ $feed->modified = $modified;
+
+ // Clear the cache.
+ cache_clear_all();
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Parses a feed and stores its items.
+ *
+ * @param $data
+ * The feed data.
+ * @param $feed
+ * An object describing the feed to be parsed.
+ *
+ * @return
+ * FALSE on error, TRUE otherwise.
+ */
+function aggregator_parse_feed(&$data, $feed) {
+ global $items, $image, $channel;
+
+ // Unset the global variables before we use them.
+ unset($GLOBALS['element'], $GLOBALS['item'], $GLOBALS['tag']);
+ $items = array();
+ $image = array();
+ $channel = array();
+
+ // Parse the data.
+ $xml_parser = drupal_xml_parser_create($data);
+ xml_set_element_handler($xml_parser, 'aggregator_element_start', 'aggregator_element_end');
+ xml_set_character_data_handler($xml_parser, 'aggregator_element_data');
+
+ if (!xml_parse($xml_parser, $data, 1)) {
+ watchdog('aggregator', 'The feed from %site seems to be broken, due to an error "%error" on line %line.', array('%site' => $feed->title, '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser)), WATCHDOG_WARNING);
+ drupal_set_message(t('The feed from %site seems to be broken, because of error "%error" on line %line.', array('%site' => $feed->title, '%error' => xml_error_string(xml_get_error_code($xml_parser)), '%line' => xml_get_current_line_number($xml_parser))), 'error');
+ return FALSE;
+ }
+ xml_parser_free($xml_parser);
+
+ // We reverse the array such that we store the first item last, and the last
+ // item first. In the database, the newest item should be at the top.
+ $items = array_reverse($items);
+
+ // Initialize items array.
+ $feed->items = array();
+ foreach ($items as $item) {
+
+ // Prepare the item:
+ foreach ($item as $key => $value) {
+ $item[$key] = trim($value);
+ }
+
+ // Resolve the item's title. If no title is found, we use up to 40
+ // characters of the description ending at a word boundary, but not
+ // splitting potential entities.
+ if (!empty($item['title'])) {
+ $item['title'] = $item['title'];
+ }
+ elseif (!empty($item['description'])) {
+ $item['title'] = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['description'], 40));
+ }
+ else {
+ $item['title'] = '';
+ }
+
+ // Resolve the items link.
+ if (!empty($item['link'])) {
+ $item['link'] = $item['link'];
+ }
+ else {
+ $item['link'] = $feed->link;
+ }
+
+ // Atom feeds have an ID tag instead of a GUID tag.
+ if (!isset($item['guid'])) {
+ $item['guid'] = isset($item['id']) ? $item['id'] : '';
+ }
+
+ // Atom feeds have a content and/or summary tag instead of a description tag.
+ if (!empty($item['content:encoded'])) {
+ $item['description'] = $item['content:encoded'];
+ }
+ elseif (!empty($item['summary'])) {
+ $item['description'] = $item['summary'];
+ }
+ elseif (!empty($item['content'])) {
+ $item['description'] = $item['content'];
+ }
+
+ // Try to resolve and parse the item's publication date.
+ $date = '';
+ foreach (array('pubdate', 'dc:date', 'dcterms:issued', 'dcterms:created', 'dcterms:modified', 'issued', 'created', 'modified', 'published', 'updated') as $key) {
+ if (!empty($item[$key])) {
+ $date = $item[$key];
+ break;
+ }
+ }
+
+ $item['timestamp'] = strtotime($date);
+
+ if ($item['timestamp'] === FALSE) {
+ $item['timestamp'] = aggregator_parse_w3cdtf($date); // Aggregator_parse_w3cdtf() returns FALSE on failure.
+ }
+
+ // Resolve dc:creator tag as the item author if author tag is not set.
+ if (empty($item['author']) && !empty($item['dc:creator'])) {
+ $item['author'] = $item['dc:creator'];
+ }
+
+ $item += array('author' => '', 'description' => '');
+
+ // Store on $feed object. This is where processors will look for parsed items.
+ $feed->items[] = $item;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Performs an action when an opening tag is encountered.
+ *
+ * Callback function used by xml_parse() within aggregator_parse_feed().
+ */
+function aggregator_element_start($parser, $name, $attributes) {
+ global $item, $element, $tag, $items, $channel;
+
+ $name = strtolower($name);
+ switch ($name) {
+ case 'image':
+ case 'textinput':
+ case 'summary':
+ case 'tagline':
+ case 'subtitle':
+ case 'logo':
+ case 'info':
+ $element = $name;
+ break;
+ case 'id':
+ case 'content':
+ if ($element != 'item') {
+ $element = $name;
+ }
+ case 'link':
+ // According to RFC 4287, link elements in Atom feeds without a 'rel'
+ // attribute should be interpreted as though the relation type is
+ // "alternate".
+ if (!empty($attributes['HREF']) && (empty($attributes['REL']) || $attributes['REL'] == 'alternate')) {
+ if ($element == 'item') {
+ $items[$item]['link'] = $attributes['HREF'];
+ }
+ else {
+ $channel['link'] = $attributes['HREF'];
+ }
+ }
+ break;
+ case 'item':
+ $element = $name;
+ $item += 1;
+ break;
+ case 'entry':
+ $element = 'item';
+ $item += 1;
+ break;
+ }
+
+ $tag = $name;
+}
+
+/**
+ * Performs an action when a closing tag is encountered.
+ *
+ * Callback function used by xml_parse() within aggregator_parse_feed().
+ */
+function aggregator_element_end($parser, $name) {
+ global $element;
+
+ switch ($name) {
+ case 'image':
+ case 'textinput':
+ case 'item':
+ case 'entry':
+ case 'info':
+ $element = '';
+ break;
+ case 'id':
+ case 'content':
+ if ($element == $name) {
+ $element = '';
+ }
+ }
+}
+
+/**
+ * Performs an action when data is encountered.
+ *
+ * Callback function used by xml_parse() within aggregator_parse_feed().
+ */
+function aggregator_element_data($parser, $data) {
+ global $channel, $element, $items, $item, $image, $tag;
+ $items += array($item => array());
+ switch ($element) {
+ case 'item':
+ $items[$item] += array($tag => '');
+ $items[$item][$tag] .= $data;
+ break;
+ case 'image':
+ case 'logo':
+ $image += array($tag => '');
+ $image[$tag] .= $data;
+ break;
+ case 'link':
+ if ($data) {
+ $items[$item] += array($tag => '');
+ $items[$item][$tag] .= $data;
+ }
+ break;
+ case 'content':
+ $items[$item] += array('content' => '');
+ $items[$item]['content'] .= $data;
+ break;
+ case 'summary':
+ $items[$item] += array('summary' => '');
+ $items[$item]['summary'] .= $data;
+ break;
+ case 'tagline':
+ case 'subtitle':
+ $channel += array('description' => '');
+ $channel['description'] .= $data;
+ break;
+ case 'info':
+ case 'id':
+ case 'textinput':
+ // The sub-element is not supported. However, we must recognize
+ // it or its contents will end up in the item array.
+ break;
+ default:
+ $channel += array($tag => '');
+ $channel[$tag] .= $data;
+ }
+}
+
+/**
+ * Parses the W3C date/time format, a subset of ISO 8601.
+ *
+ * PHP date parsing functions do not handle this format. See
+ * http://www.w3.org/TR/NOTE-datetime for more information. Originally from
+ * MagpieRSS (http://magpierss.sourceforge.net/).
+ *
+ * @param $date_str
+ * A string with a potentially W3C DTF date.
+ *
+ * @return
+ * A timestamp if parsed successfully or FALSE if not.
+ */
+function aggregator_parse_w3cdtf($date_str) {
+ if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match)) {
+ list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
+ // Calculate the epoch for current date assuming GMT.
+ $epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year);
+ if ($match[10] != 'Z') { // Z is zulu time, aka GMT
+ list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]);
+ // Zero out the variables.
+ if (!$tz_hour) {
+ $tz_hour = 0;
+ }
+ if (!$tz_min) {
+ $tz_min = 0;
+ }
+ $offset_secs = (($tz_hour * 60) + $tz_min) * 60;
+ // Is timezone ahead of GMT? If yes, subtract offset.
+ if ($tz_mod == '+') {
+ $offset_secs *= -1;
+ }
+ $epoch += $offset_secs;
+ }
+ return $epoch;
+ }
+ else {
+ return FALSE;
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.processor.inc b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.processor.inc
new file mode 100644
index 0000000..44ed549
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.processor.inc
@@ -0,0 +1,213 @@
+<?php
+
+/**
+ * @file
+ * Processor functions for the aggregator module.
+ */
+
+/**
+ * Implements hook_aggregator_process_info().
+ */
+function aggregator_aggregator_process_info() {
+ return array(
+ 'title' => t('Default processor'),
+ 'description' => t('Creates lightweight records from feed items.'),
+ );
+}
+
+/**
+ * Implements hook_aggregator_process().
+ */
+function aggregator_aggregator_process($feed) {
+ if (is_object($feed)) {
+ if (is_array($feed->items)) {
+ foreach ($feed->items as $item) {
+ // Save this item. Try to avoid duplicate entries as much as possible. If
+ // we find a duplicate entry, we resolve it and pass along its ID is such
+ // that we can update it if needed.
+ if (!empty($item['guid'])) {
+ $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND guid = :guid", array(':fid' => $feed->fid, ':guid' => $item['guid']))->fetchObject();
+ }
+ elseif ($item['link'] && $item['link'] != $feed->link && $item['link'] != $feed->url) {
+ $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND link = :link", array(':fid' => $feed->fid, ':link' => $item['link']))->fetchObject();
+ }
+ else {
+ $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND title = :title", array(':fid' => $feed->fid, ':title' => $item['title']))->fetchObject();
+ }
+ if (!$item['timestamp']) {
+ $item['timestamp'] = isset($entry->timestamp) ? $entry->timestamp : REQUEST_TIME;
+ }
+
+ // Make sure the item title and author fit in the 255 varchar column.
+ $item['title'] = truncate_utf8($item['title'], 255, TRUE, TRUE);
+ $item['author'] = truncate_utf8($item['author'], 255, TRUE, TRUE);
+ aggregator_save_item(array('iid' => (isset($entry->iid) ? $entry->iid : ''), 'fid' => $feed->fid, 'timestamp' => $item['timestamp'], 'title' => $item['title'], 'link' => $item['link'], 'author' => $item['author'], 'description' => $item['description'], 'guid' => $item['guid']));
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_aggregator_remove().
+ */
+function aggregator_aggregator_remove($feed) {
+ $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchCol();
+ if ($iids) {
+ db_delete('aggregator_category_item')
+ ->condition('iid', $iids, 'IN')
+ ->execute();
+ }
+ db_delete('aggregator_item')
+ ->condition('fid', $feed->fid)
+ ->execute();
+
+ drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed->title)));
+}
+
+/**
+ * Implements hook_form_aggregator_admin_form_alter().
+ *
+ * Form alter aggregator module's own form to keep processor functionality
+ * separate from aggregator API functionality.
+ */
+function aggregator_form_aggregator_admin_form_alter(&$form, $form_state) {
+ if (in_array('aggregator', variable_get('aggregator_processors', array('aggregator')))) {
+ $info = module_invoke('aggregator', 'aggregator_process', 'info');
+ $items = drupal_map_assoc(array(3, 5, 10, 15, 20, 25), '_aggregator_items');
+ $period = drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval');
+ $period[AGGREGATOR_CLEAR_NEVER] = t('Never');
+
+ // Only wrap into a collapsible fieldset if there is a basic configuration.
+ if (isset($form['basic_conf'])) {
+ $form['modules']['aggregator'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Default processor settings'),
+ '#description' => $info['description'],
+ '#collapsible' => TRUE,
+ '#collapsed' => !in_array('aggregator', variable_get('aggregator_processors', array('aggregator'))),
+ );
+ }
+ else {
+ $form['modules']['aggregator'] = array();
+ }
+
+ $form['modules']['aggregator']['aggregator_summary_items'] = array(
+ '#type' => 'select',
+ '#title' => t('Number of items shown in listing pages'),
+ '#default_value' => variable_get('aggregator_summary_items', 3),
+ '#empty_value' => 0,
+ '#options' => $items,
+ );
+
+ $form['modules']['aggregator']['aggregator_clear'] = array(
+ '#type' => 'select',
+ '#title' => t('Discard items older than'),
+ '#default_value' => variable_get('aggregator_clear', 9676800),
+ '#options' => $period,
+ '#description' => t('Requires a correctly configured <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))),
+ );
+
+ $form['modules']['aggregator']['aggregator_category_selector'] = array(
+ '#type' => 'radios',
+ '#title' => t('Select categories using'),
+ '#default_value' => variable_get('aggregator_category_selector', 'checkboxes'),
+ '#options' => array('checkboxes' => t('checkboxes'),
+ 'select' => t('multiple selector')),
+ '#description' => t('For a small number of categories, checkboxes are easier to use, while a multiple selector works well with large numbers of categories.'),
+ );
+ $form['modules']['aggregator']['aggregator_teaser_length'] = array(
+ '#type' => 'select',
+ '#title' => t('Length of trimmed description'),
+ '#default_value' => variable_get('aggregator_teaser_length', 600),
+ '#options' => drupal_map_assoc(array(0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000), '_aggregator_characters'),
+ '#description' => t("The maximum number of characters used in the trimmed version of content.")
+ );
+
+ }
+}
+
+/**
+ * Creates display text for teaser length option values.
+ *
+ * Callback for drupal_map_assoc() within
+ * aggregator_form_aggregator_admin_form_alter().
+ *
+ * @param $length
+ * The desired length of teaser text, in bytes.
+ *
+ * @return
+ * A translated string explaining the teaser string length.
+ */
+function _aggregator_characters($length) {
+ return ($length == 0) ? t('Unlimited') : format_plural($length, '1 character', '@count characters');
+}
+
+/**
+ * Adds/edits/deletes an aggregator item.
+ *
+ * @param $edit
+ * An associative array describing the item to be added/edited/deleted.
+ */
+function aggregator_save_item($edit) {
+ if ($edit['title'] && empty($edit['iid'])) {
+ $edit['iid'] = db_insert('aggregator_item')
+ ->fields(array(
+ 'title' => $edit['title'],
+ 'link' => $edit['link'],
+ 'author' => $edit['author'],
+ 'description' => $edit['description'],
+ 'guid' => $edit['guid'],
+ 'timestamp' => $edit['timestamp'],
+ 'fid' => $edit['fid'],
+ ))
+ ->execute();
+ }
+ if ($edit['iid'] && !$edit['title']) {
+ db_delete('aggregator_item')
+ ->condition('iid', $edit['iid'])
+ ->execute();
+ db_delete('aggregator_category_item')
+ ->condition('iid', $edit['iid'])
+ ->execute();
+ }
+ elseif ($edit['title'] && $edit['link']) {
+ // file the items in the categories indicated by the feed
+ $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $edit['fid']));
+ foreach ($result as $category) {
+ db_merge('aggregator_category_item')
+ ->key(array(
+ 'iid' => $edit['iid'],
+ 'cid' => $category->cid,
+ ))
+ ->execute();
+ }
+ }
+}
+
+/**
+ * Expires items from a feed depending on expiration settings.
+ *
+ * @param $feed
+ * Object describing feed.
+ */
+function aggregator_expire($feed) {
+ $aggregator_clear = variable_get('aggregator_clear', 9676800);
+
+ if ($aggregator_clear != AGGREGATOR_CLEAR_NEVER) {
+ // Remove all items that are older than flush item timer.
+ $age = REQUEST_TIME - $aggregator_clear;
+ $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid AND timestamp < :timestamp', array(
+ ':fid' => $feed->fid,
+ ':timestamp' => $age,
+ ))
+ ->fetchCol();
+ if ($iids) {
+ db_delete('aggregator_category_item')
+ ->condition('iid', $iids, 'IN')
+ ->execute();
+ db_delete('aggregator_item')
+ ->condition('iid', $iids, 'IN')
+ ->execute();
+ }
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.test b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.test
new file mode 100644
index 0000000..18d5b33
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/aggregator.test
@@ -0,0 +1,1019 @@
+<?php
+
+/**
+ * @file
+ * Tests for aggregator.module.
+ */
+
+/**
+ * Defines a base class for testing the Aggregator module.
+ */
+class AggregatorTestCase extends DrupalWebTestCase {
+ function setUp() {
+ parent::setUp('aggregator', 'aggregator_test');
+ $web_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create article content'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Creates an aggregator feed.
+ *
+ * This method simulates the form submission on path
+ * admin/config/services/aggregator/add/feed.
+ *
+ * @param $feed_url
+ * (optional) If given, feed will be created with this URL, otherwise
+ * /rss.xml will be used. Defaults to NULL.
+ *
+ * @return $feed
+ * Full feed object if possible.
+ *
+ * @see getFeedEditArray()
+ */
+ function createFeed($feed_url = NULL) {
+ $edit = $this->getFeedEditArray($feed_url);
+ $this->drupalPost('admin/config/services/aggregator/add/feed', $edit, t('Save'));
+ $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), format_string('The feed !name has been added.', array('!name' => $edit['title'])));
+
+ $feed = db_query("SELECT * FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title'], ':url' => $edit['url']))->fetch();
+ $this->assertTrue(!empty($feed), 'The feed found in database.');
+ return $feed;
+ }
+
+ /**
+ * Deletes an aggregator feed.
+ *
+ * @param $feed
+ * Feed object representing the feed.
+ */
+ function deleteFeed($feed) {
+ $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->fid, array(), t('Delete'));
+ $this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->title)), 'Feed deleted successfully.');
+ }
+
+ /**
+ * Returns a randomly generated feed edit array.
+ *
+ * @param $feed_url
+ * (optional) If given, feed will be created with this URL, otherwise
+ * /rss.xml will be used. Defaults to NULL.
+ * @return
+ * A feed array.
+ */
+ function getFeedEditArray($feed_url = NULL) {
+ $feed_name = $this->randomName(10);
+ if (!$feed_url) {
+ $feed_url = url('rss.xml', array(
+ 'query' => array('feed' => $feed_name),
+ 'absolute' => TRUE,
+ ));
+ }
+ $edit = array(
+ 'title' => $feed_name,
+ 'url' => $feed_url,
+ 'refresh' => '900',
+ );
+ return $edit;
+ }
+
+ /**
+ * Returns the count of the randomly created feed array.
+ *
+ * @return
+ * Number of feed items on default feed created by createFeed().
+ */
+ function getDefaultFeedItemCount() {
+ // Our tests are based off of rss.xml, so let's find out how many elements should be related.
+ $feed_count = db_query_range('SELECT COUNT(*) FROM {node} n WHERE n.promote = 1 AND n.status = 1', 0, variable_get('feed_default_items', 10))->fetchField();
+ return $feed_count > 10 ? 10 : $feed_count;
+ }
+
+ /**
+ * Updates the feed items.
+ *
+ * This method simulates a click to
+ * admin/config/services/aggregator/update/$fid.
+ *
+ * @param $feed
+ * Feed object representing the feed, passed by reference.
+ * @param $expected_count
+ * Expected number of feed items.
+ */
+ function updateFeedItems(&$feed, $expected_count) {
+ // First, let's ensure we can get to the rss xml.
+ $this->drupalGet($feed->url);
+ $this->assertResponse(200, format_string('!url is reachable.', array('!url' => $feed->url)));
+
+ // Attempt to access the update link directly without an access token.
+ $this->drupalGet('admin/config/services/aggregator/update/' . $feed->fid);
+ $this->assertResponse(403);
+
+ // Refresh the feed (simulated link click).
+ $this->drupalGet('admin/config/services/aggregator');
+ $this->clickLink('update items');
+
+ // Ensure we have the right number of items.
+ $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid));
+ $items = array();
+ $feed->items = array();
+ foreach ($result as $item) {
+ $feed->items[] = $item->iid;
+ }
+ $feed->item_count = count($feed->items);
+ $this->assertEqual($expected_count, $feed->item_count, format_string('Total items in feed equal to the total items in database (!val1 != !val2)', array('!val1' => $expected_count, '!val2' => $feed->item_count)));
+ }
+
+ /**
+ * Confirms an item removal from a feed.
+ *
+ * @param $feed
+ * Feed object representing the feed.
+ */
+ function removeFeedItems($feed) {
+ $this->drupalPost('admin/config/services/aggregator/remove/' . $feed->fid, array(), t('Remove items'));
+ $this->assertRaw(t('The news items from %title have been removed.', array('%title' => $feed->title)), 'Feed items removed.');
+ }
+
+ /**
+ * Adds and removes feed items and ensure that the count is zero.
+ *
+ * @param $feed
+ * Feed object representing the feed.
+ * @param $expected_count
+ * Expected number of feed items.
+ */
+ function updateAndRemove($feed, $expected_count) {
+ $this->updateFeedItems($feed, $expected_count);
+ $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField();
+ $this->assertTrue($count);
+ $this->removeFeedItems($feed);
+ $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField();
+ $this->assertTrue($count == 0);
+ }
+
+ /**
+ * Pulls feed categories from {aggregator_category_feed} table.
+ *
+ * @param $feed
+ * Feed object representing the feed.
+ */
+ function getFeedCategories($feed) {
+ // add the categories to the feed so we can use them
+ $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $feed->fid));
+ foreach ($result as $category) {
+ $feed->categories[] = $category->cid;
+ }
+ }
+
+ /**
+ * Pulls categories from {aggregator_category} table.
+ *
+ * @return
+ * An associative array keyed by category ID and values are set to the
+ * category names.
+ */
+ function getCategories() {
+ $categories = array();
+ $result = db_query('SELECT * FROM {aggregator_category}');
+ foreach ($result as $category) {
+ $categories[$category->cid] = $category;
+ }
+ return $categories;
+ }
+
+ /**
+ * Checks whether the feed name and URL are unique.
+ *
+ * @param $feed_name
+ * String containing the feed name to check.
+ * @param $feed_url
+ * String containing the feed URL to check.
+ *
+ * @return
+ * TRUE if feed is unique.
+ */
+ function uniqueFeed($feed_name, $feed_url) {
+ $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed_name, ':url' => $feed_url))->fetchField();
+ return (1 == $result);
+ }
+
+ /**
+ * Creates a valid OPML file from an array of feeds.
+ *
+ * @param $feeds
+ * An array of feeds.
+ *
+ * @return
+ * Path to valid OPML file.
+ */
+ function getValidOpml($feeds) {
+ // Properly escape URLs so that XML parsers don't choke on them.
+ foreach ($feeds as &$feed) {
+ $feed['url'] = htmlspecialchars($feed['url']);
+ }
+ /**
+ * Does not have an XML declaration, must pass the parser.
+ */
+ $opml = <<<EOF
+<opml version="1.0">
+ <head></head>
+ <body>
+ <!-- First feed to be imported. -->
+ <outline text="{$feeds[0]['title']}" xmlurl="{$feeds[0]['url']}" />
+
+ <!-- Second feed. Test string delimitation and attribute order. -->
+ <outline xmlurl='{$feeds[1]['url']}' text='{$feeds[1]['title']}'/>
+
+ <!-- Test for duplicate URL and title. -->
+ <outline xmlurl="{$feeds[0]['url']}" text="Duplicate URL"/>
+ <outline xmlurl="http://duplicate.title" text="{$feeds[1]['title']}"/>
+
+ <!-- Test that feeds are only added with required attributes. -->
+ <outline text="{$feeds[2]['title']}" />
+ <outline xmlurl="{$feeds[2]['url']}" />
+ </body>
+</opml>
+EOF;
+
+ $path = 'public://valid-opml.xml';
+ return file_unmanaged_save_data($opml, $path);
+ }
+
+ /**
+ * Creates an invalid OPML file.
+ *
+ * @return
+ * Path to invalid OPML file.
+ */
+ function getInvalidOpml() {
+ $opml = <<<EOF
+<opml>
+ <invalid>
+</opml>
+EOF;
+
+ $path = 'public://invalid-opml.xml';
+ return file_unmanaged_save_data($opml, $path);
+ }
+
+ /**
+ * Creates a valid but empty OPML file.
+ *
+ * @return
+ * Path to empty OPML file.
+ */
+ function getEmptyOpml() {
+ $opml = <<<EOF
+<?xml version="1.0" encoding="utf-8"?>
+<opml version="1.0">
+ <head></head>
+ <body>
+ <outline text="Sample text" />
+ <outline text="Sample text" url="Sample URL" />
+ </body>
+</opml>
+EOF;
+
+ $path = 'public://empty-opml.xml';
+ return file_unmanaged_save_data($opml, $path);
+ }
+
+ function getRSS091Sample() {
+ return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_rss091.xml';
+ }
+
+ function getAtomSample() {
+ // The content of this sample ATOM feed is based directly off of the
+ // example provided in RFC 4287.
+ return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_atom.xml';
+ }
+
+ /**
+ * Creates sample article nodes.
+ *
+ * @param $count
+ * (optional) The number of nodes to generate. Defaults to five.
+ */
+ function createSampleNodes($count = 5) {
+ $langcode = LANGUAGE_NONE;
+ // Post $count article nodes.
+ for ($i = 0; $i < $count; $i++) {
+ $edit = array();
+ $edit['title'] = $this->randomName();
+ $edit["body[$langcode][0][value]"] = $this->randomName();
+ $this->drupalPost('node/add/article', $edit, t('Save'));
+ }
+ }
+}
+
+/**
+ * Tests functionality of the configuration settings in the Aggregator module.
+ */
+class AggregatorConfigurationTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Aggregator configuration',
+ 'description' => 'Test aggregator settings page.',
+ 'group' => 'Aggregator',
+ );
+ }
+
+ /**
+ * Tests the settings form to ensure the correct default values are used.
+ */
+ function testSettingsPage() {
+ $edit = array(
+ 'aggregator_allowed_html_tags' => '<a>',
+ 'aggregator_summary_items' => 10,
+ 'aggregator_clear' => 3600,
+ 'aggregator_category_selector' => 'select',
+ 'aggregator_teaser_length' => 200,
+ );
+ $this->drupalPost('admin/config/services/aggregator/settings', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'));
+
+ foreach ($edit as $name => $value) {
+ $this->assertFieldByName($name, $value, format_string('"@name" has correct default value.', array('@name' => $name)));
+ }
+ }
+}
+
+/**
+ * Tests adding aggregator feeds.
+ */
+class AddFeedTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Add feed functionality',
+ 'description' => 'Add feed test.',
+ 'group' => 'Aggregator'
+ );
+ }
+
+ /**
+ * Creates and ensures that a feed is unique, checks source, and deletes feed.
+ */
+ function testAddFeed() {
+ $feed = $this->createFeed();
+
+ // Check feed data.
+ $this->assertEqual($this->getUrl(), url('admin/config/services/aggregator/add/feed', array('absolute' => TRUE)), 'Directed to correct url.');
+ $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), 'The feed is unique.');
+
+ // Check feed source.
+ $this->drupalGet('aggregator/sources/' . $feed->fid);
+ $this->assertResponse(200, 'Feed source exists.');
+ $this->assertText($feed->title, 'Page title');
+ $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize');
+ $this->assertResponse(200, 'Feed categorization page exists.');
+
+ // Delete feed.
+ $this->deleteFeed($feed);
+ }
+
+ /**
+ * Tests feeds with very long URLs.
+ */
+ function testAddLongFeed() {
+ // Create a feed with a URL of > 255 characters.
+ $long_url = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889&ix=heb";
+ $feed = $this->createFeed($long_url);
+
+ // Create a second feed of > 255 characters, where the only difference is
+ // after the 255th character.
+ $long_url_2 = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889";
+ $feed_2 = $this->createFeed($long_url_2);
+
+ // Check feed data.
+ $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), 'The first long URL feed is unique.');
+ $this->assertTrue($this->uniqueFeed($feed_2->title, $feed_2->url), 'The second long URL feed is unique.');
+
+ // Check feed source.
+ $this->drupalGet('aggregator/sources/' . $feed->fid);
+ $this->assertResponse(200, 'Long URL feed source exists.');
+ $this->assertText($feed->title, 'Page title');
+ $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize');
+ $this->assertResponse(200, 'Long URL feed categorization page exists.');
+
+ // Delete feeds.
+ $this->deleteFeed($feed);
+ $this->deleteFeed($feed_2);
+ }
+}
+
+/**
+ * Tests the categorize feed functionality in the Aggregator module.
+ */
+class CategorizeFeedTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Categorize feed functionality',
+ 'description' => 'Categorize feed test.',
+ 'group' => 'Aggregator'
+ );
+ }
+
+ /**
+ * Creates a feed and makes sure you can add more than one category to it.
+ */
+ function testCategorizeFeed() {
+
+ // Create 2 categories.
+ $category_1 = array('title' => $this->randomName(10), 'description' => '');
+ $this->drupalPost('admin/config/services/aggregator/add/category', $category_1, t('Save'));
+ $this->assertRaw(t('The category %title has been added.', array('%title' => $category_1['title'])), format_string('The category %title has been added.', array('%title' => $category_1['title'])));
+
+ $category_2 = array('title' => $this->randomName(10), 'description' => '');
+ $this->drupalPost('admin/config/services/aggregator/add/category', $category_2, t('Save'));
+ $this->assertRaw(t('The category %title has been added.', array('%title' => $category_2['title'])), format_string('The category %title has been added.', array('%title' => $category_2['title'])));
+
+ // Get categories from database.
+ $categories = $this->getCategories();
+
+ // Create a feed and assign 2 categories to it.
+ $feed = $this->getFeedEditArray();
+ $feed['block'] = 5;
+ foreach ($categories as $cid => $category) {
+ $feed['category'][$cid] = $cid;
+ }
+
+ // Use aggregator_save_feed() function to save the feed.
+ aggregator_save_feed($feed);
+ $db_feed = db_query("SELECT * FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed['title'], ':url' => $feed['url']))->fetch();
+
+ // Assert the feed has two categories.
+ $this->getFeedCategories($db_feed);
+ $this->assertEqual(count($db_feed->categories), 2, 'Feed has 2 categories');
+ }
+}
+
+/**
+ * Tests functionality of updating the feed in the Aggregator module.
+ */
+class UpdateFeedTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Update feed functionality',
+ 'description' => 'Update feed test.',
+ 'group' => 'Aggregator'
+ );
+ }
+
+ /**
+ * Creates a feed and attempts to update it.
+ */
+ function testUpdateFeed() {
+ $remamining_fields = array('title', 'url', '');
+ foreach ($remamining_fields as $same_field) {
+ $feed = $this->createFeed();
+
+ // Get new feed data array and modify newly created feed.
+ $edit = $this->getFeedEditArray();
+ $edit['refresh'] = 1800; // Change refresh value.
+ if (isset($feed->{$same_field})) {
+ $edit[$same_field] = $feed->{$same_field};
+ }
+ $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->fid, $edit, t('Save'));
+ $this->assertRaw(t('The feed %name has been updated.', array('%name' => $edit['title'])), format_string('The feed %name has been updated.', array('%name' => $edit['title'])));
+
+ // Check feed data.
+ $this->assertEqual($this->getUrl(), url('admin/config/services/aggregator/', array('absolute' => TRUE)));
+ $this->assertTrue($this->uniqueFeed($edit['title'], $edit['url']), 'The feed is unique.');
+
+ // Check feed source.
+ $this->drupalGet('aggregator/sources/' . $feed->fid);
+ $this->assertResponse(200, 'Feed source exists.');
+ $this->assertText($edit['title'], 'Page title');
+
+ // Delete feed.
+ $feed->title = $edit['title']; // Set correct title so deleteFeed() will work.
+ $this->deleteFeed($feed);
+ }
+ }
+}
+
+/**
+ * Tests functionality for removing feeds in the Aggregator module.
+ */
+class RemoveFeedTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Remove feed functionality',
+ 'description' => 'Remove feed test.',
+ 'group' => 'Aggregator'
+ );
+ }
+
+ /**
+ * Removes a feed and ensures that all of its services are removed.
+ */
+ function testRemoveFeed() {
+ $feed = $this->createFeed();
+
+ // Delete feed.
+ $this->deleteFeed($feed);
+
+ // Check feed source.
+ $this->drupalGet('aggregator/sources/' . $feed->fid);
+ $this->assertResponse(404, 'Deleted feed source does not exists.');
+
+ // Check database for feed.
+ $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed->title, ':url' => $feed->url))->fetchField();
+ $this->assertFalse($result, 'Feed not found in database');
+ }
+}
+
+/**
+ * Tests functionality of updating a feed item in the Aggregator module.
+ */
+class UpdateFeedItemTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Update feed item functionality',
+ 'description' => 'Update feed items from a feed.',
+ 'group' => 'Aggregator'
+ );
+ }
+
+ /**
+ * Tests running "update items" from 'admin/config/services/aggregator' page.
+ */
+ function testUpdateFeedItem() {
+ $this->createSampleNodes();
+
+ // Create a feed and test updating feed items if possible.
+ $feed = $this->createFeed();
+ if (!empty($feed)) {
+ $this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
+ $this->removeFeedItems($feed);
+ }
+
+ // Delete feed.
+ $this->deleteFeed($feed);
+
+ // Test updating feed items without valid timestamp information.
+ $edit = array(
+ 'title' => "Feed without publish timestamp",
+ 'url' => $this->getRSS091Sample(),
+ );
+
+ $this->drupalGet($edit['url']);
+ $this->assertResponse(array(200), format_string('URL !url is accessible', array('!url' => $edit['url'])));
+
+ $this->drupalPost('admin/config/services/aggregator/add/feed', $edit, t('Save'));
+ $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), format_string('The feed !name has been added.', array('!name' => $edit['title'])));
+
+ $feed = db_query("SELECT * FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url']))->fetchObject();
+
+ aggregator_refresh($feed);
+ $before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField();
+
+ // Sleep for 3 second.
+ sleep(3);
+ db_update('aggregator_feed')
+ ->condition('fid', $feed->fid)
+ ->fields(array(
+ 'checked' => 0,
+ 'hash' => '',
+ 'etag' => '',
+ 'modified' => 0,
+ ))
+ ->execute();
+ aggregator_refresh($feed);
+
+ $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField();
+ $this->assertTrue($before === $after, format_string('Publish timestamp of feed item was not updated (!before === !after)', array('!before' => $before, '!after' => $after)));
+ }
+}
+
+class RemoveFeedItemTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Remove feed item functionality',
+ 'description' => 'Remove feed items from a feed.',
+ 'group' => 'Aggregator'
+ );
+ }
+
+ /**
+ * Tests running "remove items" from 'admin/config/services/aggregator' page.
+ */
+ function testRemoveFeedItem() {
+ // Create a bunch of test feeds.
+ $feed_urls = array();
+ // No last-modified, no etag.
+ $feed_urls[] = url('aggregator/test-feed', array('absolute' => TRUE));
+ // Last-modified, but no etag.
+ $feed_urls[] = url('aggregator/test-feed/1', array('absolute' => TRUE));
+ // No Last-modified, but etag.
+ $feed_urls[] = url('aggregator/test-feed/0/1', array('absolute' => TRUE));
+ // Last-modified and etag.
+ $feed_urls[] = url('aggregator/test-feed/1/1', array('absolute' => TRUE));
+
+ foreach ($feed_urls as $feed_url) {
+ $feed = $this->createFeed($feed_url);
+ // Update and remove items two times in a row to make sure that removal
+ // resets all 'modified' information (modified, etag, hash) and allows for
+ // immediate update.
+ $this->updateAndRemove($feed, 4);
+ $this->updateAndRemove($feed, 4);
+ $this->updateAndRemove($feed, 4);
+ // Delete feed.
+ $this->deleteFeed($feed);
+ }
+ }
+}
+
+/**
+ * Tests categorization functionality in the Aggregator module.
+ */
+class CategorizeFeedItemTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Categorize feed item functionality',
+ 'description' => 'Test feed item categorization.',
+ 'group' => 'Aggregator'
+ );
+ }
+
+ /**
+ * Checks that children of a feed inherit a defined category.
+ *
+ * If a feed has a category, make sure that the children inherit that
+ * categorization.
+ */
+ function testCategorizeFeedItem() {
+ $this->createSampleNodes();
+
+ // Simulate form submission on "admin/config/services/aggregator/add/category".
+ $edit = array('title' => $this->randomName(10), 'description' => '');
+ $this->drupalPost('admin/config/services/aggregator/add/category', $edit, t('Save'));
+ $this->assertRaw(t('The category %title has been added.', array('%title' => $edit['title'])), format_string('The category %title has been added.', array('%title' => $edit['title'])));
+
+ $category = db_query("SELECT * FROM {aggregator_category} WHERE title = :title", array(':title' => $edit['title']))->fetch();
+ $this->assertTrue(!empty($category), 'The category found in database.');
+
+ $link_path = 'aggregator/categories/' . $category->cid;
+ $menu_link = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $link_path))->fetch();
+ $this->assertTrue(!empty($menu_link), 'The menu link associated with the category found in database.');
+
+ $feed = $this->createFeed();
+ db_insert('aggregator_category_feed')
+ ->fields(array(
+ 'cid' => $category->cid,
+ 'fid' => $feed->fid,
+ ))
+ ->execute();
+ $this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
+ $this->getFeedCategories($feed);
+ $this->assertTrue(!empty($feed->categories), 'The category found in the feed.');
+
+ // For each category of a feed, ensure feed items have that category, too.
+ if (!empty($feed->categories) && !empty($feed->items)) {
+ foreach ($feed->categories as $category) {
+ $categorized_count = db_select('aggregator_category_item')
+ ->condition('iid', $feed->items, 'IN')
+ ->countQuery()
+ ->execute()
+ ->fetchField();
+
+ $this->assertEqual($feed->item_count, $categorized_count, 'Total items in feed equal to the total categorized feed items in database');
+ }
+ }
+
+ // Delete feed.
+ $this->deleteFeed($feed);
+ }
+}
+
+/**
+ * Tests importing feeds from OPML functionality for the Aggregator module.
+ */
+class ImportOPMLTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Import feeds from OPML functionality',
+ 'description' => 'Test OPML import.',
+ 'group' => 'Aggregator',
+ );
+ }
+
+ /**
+ * Opens OPML import form.
+ */
+ function openImportForm() {
+ db_delete('aggregator_category')->execute();
+
+ $category = $this->randomName(10);
+ $cid = db_insert('aggregator_category')
+ ->fields(array(
+ 'title' => $category,
+ 'description' => '',
+ ))
+ ->execute();
+
+ $this->drupalGet('admin/config/services/aggregator/add/opml');
+ $this->assertText('A single OPML document may contain a collection of many feeds.', 'Found OPML help text.');
+ $this->assertField('files[upload]', 'Found file upload field.');
+ $this->assertField('remote', 'Found Remote URL field.');
+ $this->assertField('refresh', 'Found Refresh field.');
+ $this->assertFieldByName("category[$cid]", $cid, 'Found category field.');
+ }
+
+ /**
+ * Submits form filled with invalid fields.
+ */
+ function validateImportFormFields() {
+ $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
+
+ $edit = array();
+ $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
+ $this->assertRaw(t('You must <em>either</em> upload a file or enter a URL.'), 'Error if no fields are filled.');
+
+ $path = $this->getEmptyOpml();
+ $edit = array(
+ 'files[upload]' => $path,
+ 'remote' => file_create_url($path),
+ );
+ $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
+ $this->assertRaw(t('You must <em>either</em> upload a file or enter a URL.'), 'Error if both fields are filled.');
+
+ $edit = array('remote' => 'invalidUrl://empty');
+ $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
+ $this->assertText(t('This URL is not valid.'), 'Error if the URL is invalid.');
+
+ $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
+ $this->assertEqual($before, $after, 'No feeds were added during the three last form submissions.');
+ }
+
+ /**
+ * Submits form with invalid, empty, and valid OPML files.
+ */
+ function submitImportForm() {
+ $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
+
+ $form['files[upload]'] = $this->getInvalidOpml();
+ $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import'));
+ $this->assertText(t('No new feed has been added.'), 'Attempting to upload invalid XML.');
+
+ $edit = array('remote' => file_create_url($this->getEmptyOpml()));
+ $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
+ $this->assertText(t('No new feed has been added.'), 'Attempting to load empty OPML from remote URL.');
+
+ $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
+ $this->assertEqual($before, $after, 'No feeds were added during the two last form submissions.');
+
+ db_delete('aggregator_feed')->execute();
+ db_delete('aggregator_category')->execute();
+ db_delete('aggregator_category_feed')->execute();
+
+ $category = $this->randomName(10);
+ db_insert('aggregator_category')
+ ->fields(array(
+ 'cid' => 1,
+ 'title' => $category,
+ 'description' => '',
+ ))
+ ->execute();
+
+ $feeds[0] = $this->getFeedEditArray();
+ $feeds[1] = $this->getFeedEditArray();
+ $feeds[2] = $this->getFeedEditArray();
+ $edit = array(
+ 'files[upload]' => $this->getValidOpml($feeds),
+ 'refresh' => '900',
+ 'category[1]' => $category,
+ );
+ $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
+ $this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['url'])), 'Verifying that a duplicate URL was identified');
+ $this->assertRaw(t('A feed named %title already exists.', array('%title' => $feeds[1]['title'])), 'Verifying that a duplicate title was identified');
+
+ $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
+ $this->assertEqual($after, 2, 'Verifying that two distinct feeds were added.');
+
+ $feeds_from_db = db_query("SELECT f.title, f.url, f.refresh, cf.cid FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} cf ON f.fid = cf.fid");
+ $refresh = $category = TRUE;
+ foreach ($feeds_from_db as $feed) {
+ $title[$feed->url] = $feed->title;
+ $url[$feed->title] = $feed->url;
+ $category = $category && $feed->cid == 1;
+ $refresh = $refresh && $feed->refresh == 900;
+ }
+
+ $this->assertEqual($title[$feeds[0]['url']], $feeds[0]['title'], 'First feed was added correctly.');
+ $this->assertEqual($url[$feeds[1]['title']], $feeds[1]['url'], 'Second feed was added correctly.');
+ $this->assertTrue($refresh, 'Refresh times are correct.');
+ $this->assertTrue($category, 'Categories are correct.');
+ }
+
+ /**
+ * Tests the import of an OPML file.
+ */
+ function testOPMLImport() {
+ $this->openImportForm();
+ $this->validateImportFormFields();
+ $this->submitImportForm();
+ }
+}
+
+/**
+ * Tests functionality of the cron process in the Aggregator module.
+ */
+class AggregatorCronTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Update on cron functionality',
+ 'description' => 'Update feeds on cron.',
+ 'group' => 'Aggregator'
+ );
+ }
+
+ /**
+ * Adds feeds and updates them via cron process.
+ */
+ public function testCron() {
+ // Create feed and test basic updating on cron.
+ global $base_url;
+ $key = variable_get('cron_key', 'drupal');
+ $this->createSampleNodes();
+ $feed = $this->createFeed();
+ $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
+ $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
+ $this->removeFeedItems($feed);
+ $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
+ $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
+ $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
+
+ // Test feed locking when queued for update.
+ $this->removeFeedItems($feed);
+ db_update('aggregator_feed')
+ ->condition('fid', $feed->fid)
+ ->fields(array(
+ 'queued' => REQUEST_TIME,
+ ))
+ ->execute();
+ $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
+ $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
+ db_update('aggregator_feed')
+ ->condition('fid', $feed->fid)
+ ->fields(array(
+ 'queued' => 0,
+ ))
+ ->execute();
+ $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
+ $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
+ }
+}
+
+/**
+ * Tests rendering functionality in the Aggregator module.
+ */
+class AggregatorRenderingTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Checks display of aggregator items',
+ 'description' => 'Checks display of aggregator items on the page.',
+ 'group' => 'Aggregator'
+ );
+ }
+
+ /**
+ * Adds a feed block to the page and checks its links.
+ *
+ * @todo Test the category block as well.
+ */
+ public function testBlockLinks() {
+ // Create feed.
+ $this->createSampleNodes();
+ $feed = $this->createFeed();
+ $this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
+
+ // Place block on page (@see block.test:moveBlockToRegion())
+ // Need admin user to be able to access block admin.
+ $this->admin_user = $this->drupalCreateUser(array(
+ 'administer blocks',
+ 'access administration pages',
+ 'administer news feeds',
+ 'access news feeds',
+ ));
+ $this->drupalLogin($this->admin_user);
+
+ // Prepare to use the block admin form.
+ $block = array(
+ 'module' => 'aggregator',
+ 'delta' => 'feed-' . $feed->fid,
+ 'title' => $feed->title,
+ );
+ $region = 'footer';
+ $edit = array();
+ $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $region;
+ // Check the feed block is available in the block list form.
+ $this->drupalGet('admin/structure/block');
+ $this->assertFieldByName('blocks[' . $block['module'] . '_' . $block['delta'] . '][region]', '', 'Aggregator feed block is available for positioning.');
+ // Position it.
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $this->assertText(t('The block settings have been updated.'), format_string('Block successfully moved to %region_name region.', array( '%region_name' => $region)));
+ // Confirm that the block is now being displayed on pages.
+ $this->drupalGet('node');
+ $this->assertText(t($block['title']), 'Feed block is displayed on the page.');
+
+ // Find the expected read_more link.
+ $href = 'aggregator/sources/' . $feed->fid;
+ $links = $this->xpath('//a[@href = :href]', array(':href' => url($href)));
+ $this->assert(isset($links[0]), format_string('Link to href %href found.', array('%href' => $href)));
+
+ // Visit that page.
+ $this->drupalGet($href);
+ $correct_titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => $feed->title));
+ $this->assertFalse(empty($correct_titles), 'Aggregator feed page is available and has the correct title.');
+
+ // Set the number of news items to 0 to test that the block does not show
+ // up.
+ $feed->block = 0;
+ aggregator_save_feed((array) $feed);
+ // It is necessary to flush the cache after saving the number of items.
+ drupal_flush_all_caches();
+ // Check that the block is no longer displayed.
+ $this->drupalGet('node');
+ $this->assertNoText(t($block['title']), 'Feed block is not displayed on the page when number of items is set to 0.');
+ }
+
+ /**
+ * Creates a feed and checks that feed's page.
+ */
+ public function testFeedPage() {
+ // Increase the number of items published in the rss.xml feed so we have
+ // enough articles to test paging.
+ variable_set('feed_default_items', 30);
+
+ // Create a feed with 30 items.
+ $this->createSampleNodes(30);
+ $feed = $this->createFeed();
+ $this->updateFeedItems($feed, 30);
+
+ // Check for the presence of a pager.
+ $this->drupalGet('aggregator/sources/' . $feed->fid);
+ $elements = $this->xpath("//ul[@class=:class]", array(':class' => 'pager'));
+ $this->assertTrue(!empty($elements), 'Individual source page contains a pager.');
+
+ // Reset the number of items in rss.xml to the default value.
+ variable_set('feed_default_items', 10);
+ }
+}
+
+/**
+ * Tests feed parsing in the Aggregator module.
+ */
+class FeedParserTestCase extends AggregatorTestCase {
+ public static function getInfo() {
+ return array(
+ 'name' => 'Feed parser functionality',
+ 'description' => 'Test the built-in feed parser with valid feed samples.',
+ 'group' => 'Aggregator',
+ );
+ }
+
+ function setUp() {
+ parent::setUp();
+ // Do not remove old aggregator items during these tests, since our sample
+ // feeds have hardcoded dates in them (which may be expired when this test
+ // is run).
+ variable_set('aggregator_clear', AGGREGATOR_CLEAR_NEVER);
+ }
+
+ /**
+ * Tests a feed that uses the RSS 0.91 format.
+ */
+ function testRSS091Sample() {
+ $feed = $this->createFeed($this->getRSS091Sample());
+ aggregator_refresh($feed);
+ $this->drupalGet('aggregator/sources/' . $feed->fid);
+ $this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->title)));
+ $this->assertText('First example feed item title');
+ $this->assertLinkByHref('http://example.com/example-turns-one');
+ $this->assertText('First example feed item description.');
+
+ // Several additional items that include elements over 255 characters.
+ $this->assertRaw("Second example feed item title.");
+ $this->assertText('Long link feed item title');
+ $this->assertText('Long link feed item description');
+ $this->assertLinkByHref('http://example.com/tomorrow/and/tomorrow/and/tomorrow/creeps/in/this/petty/pace/from/day/to/day/to/the/last/syllable/of/recorded/time/and/all/our/yesterdays/have/lighted/fools/the/way/to/dusty/death/out/out/brief/candle/life/is/but/a/walking/shadow/a/poor/player/that/struts/and/frets/his/hour/upon/the/stage/and/is/heard/no/more/it/is/a/tale/told/by/an/idiot/full/of/sound/and/fury/signifying/nothing');
+ $this->assertText('Long author feed item title');
+ $this->assertText('Long author feed item description');
+ $this->assertLinkByHref('http://example.com/long/author');
+ }
+
+ /**
+ * Tests a feed that uses the Atom format.
+ */
+ function testAtomSample() {
+ $feed = $this->createFeed($this->getAtomSample());
+ aggregator_refresh($feed);
+ $this->drupalGet('aggregator/sources/' . $feed->fid);
+ $this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->title)));
+ $this->assertText('Atom-Powered Robots Run Amok');
+ $this->assertLinkByHref('http://example.org/2003/12/13/atom03');
+ $this->assertText('Some text.');
+ $this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(':link' => 'http://example.org/2003/12/13/atom03'))->fetchField(), 'Atom entry id element is parsed correctly.');
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test.info b/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test.info
new file mode 100644
index 0000000..a122ba2
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test.info
@@ -0,0 +1,12 @@
+name = "Aggregator module tests"
+description = "Support module for aggregator related testing."
+package = Testing
+version = VERSION
+core = 7.x
+hidden = TRUE
+
+; Information added by Drupal.org packaging script on 2014-01-15
+version = "7.26"
+project = "drupal"
+datestamp = "1389815930"
+
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test.module b/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test.module
new file mode 100644
index 0000000..2d26a5d
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test.module
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Implements hook_menu().
+ */
+function aggregator_test_menu() {
+ $items['aggregator/test-feed'] = array(
+ 'title' => 'Test feed static last modified date',
+ 'description' => "A cached test feed with a static last modified date.",
+ 'page callback' => 'aggregator_test_feed',
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+}
+
+/**
+ * Page callback. Generates a test feed and simulates last-modified and etags.
+ *
+ * @param $use_last_modified
+ * Set TRUE to send a last modified header.
+ * @param $use_etag
+ * Set TRUE to send an etag.
+ */
+function aggregator_test_feed($use_last_modified = FALSE, $use_etag = FALSE) {
+ $last_modified = strtotime('Sun, 19 Nov 1978 05:00:00 GMT');
+ $etag = drupal_hash_base64($last_modified);
+
+ $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE;
+ $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE;
+
+ // Send appropriate response. We respond with a 304 not modified on either
+ // etag or on last modified.
+ if ($use_last_modified) {
+ drupal_add_http_header('Last-Modified', gmdate(DATE_RFC1123, $last_modified));
+ }
+ if ($use_etag) {
+ drupal_add_http_header('ETag', $etag);
+ }
+ // Return 304 not modified if either last modified or etag match.
+ if ($last_modified == $if_modified_since || $etag == $if_none_match) {
+ drupal_add_http_header('Status', '304 Not Modified');
+ return;
+ }
+
+ // The following headers force validation of cache:
+ drupal_add_http_header('Expires', 'Sun, 19 Nov 1978 05:00:00 GMT');
+ drupal_add_http_header('Cache-Control', 'must-revalidate');
+ drupal_add_http_header('Content-Type', 'application/rss+xml; charset=utf-8');
+
+ // Read actual feed from file.
+ $file_name = DRUPAL_ROOT . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_rss091.xml';
+ $handle = fopen($file_name, 'r');
+ $feed = fread($handle, filesize($file_name));
+ fclose($handle);
+
+ print $feed;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test_atom.xml b/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test_atom.xml
new file mode 100644
index 0000000..357b2e5
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test_atom.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+
+ <title>Example Feed</title>
+ <link href="http://example.org/" />
+ <updated>2003-12-13T18:30:02Z</updated>
+ <author>
+ <name>John Doe</name>
+ </author>
+ <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
+
+ <entry>
+ <title>Atom-Powered Robots Run Amok</title>
+ <link href="http://example.org/2003/12/13/atom03" />
+ <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
+ <updated>2003-12-13T18:30:02Z</updated>
+ <summary>Some text.</summary>
+ </entry>
+
+</feed>
diff --git a/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test_rss091.xml b/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test_rss091.xml
new file mode 100644
index 0000000..2944022
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/aggregator/tests/aggregator_test_rss091.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="0.91">
+ <channel>
+ <title>Example</title>
+ <link>http://example.com</link>
+ <description>Example updates</description>
+ <language>en-us</language>
+ <copyright>Copyright 2000, Example team.</copyright>
+ <managingEditor>editor@example.com</managingEditor>
+ <webMaster>webmaster@example.com</webMaster>
+ <image>
+ <title>Example</title>
+ <url>http://example.com/images/druplicon.png</url>
+ <link>http://example.com</link>
+ <width>88</width>
+ <height>100</height>
+ <description>Example updates</description>
+ </image>
+ <item>
+ <title>First example feed item title</title>
+ <link>http://example.com/example-turns-one</link>
+ <description>First example feed item description.</description>
+ </item>
+ <item>
+ <title>Second example feed item title. This title is extremely long so that it exceeds the 255 character limit for titles in feed item storage. In fact it's so long that this sentence isn't long enough so I'm rambling a bit to make it longer, nearly there now. Ah now it's long enough so I'll shut up.</title>
+ <link>http://example.com/example-turns-two</link>
+ <description>Second example feed item description.</description>
+ </item>
+ <item>
+ <title>Long link feed item title.</title>
+ <link>http://example.com/tomorrow/and/tomorrow/and/tomorrow/creeps/in/this/petty/pace/from/day/to/day/to/the/last/syllable/of/recorded/time/and/all/our/yesterdays/have/lighted/fools/the/way/to/dusty/death/out/out/brief/candle/life/is/but/a/walking/shadow/a/poor/player/that/struts/and/frets/his/hour/upon/the/stage/and/is/heard/no/more/it/is/a/tale/told/by/an/idiot/full/of/sound/and/fury/signifying/nothing</link>
+ <description>Long link feed item description.</description>
+ </item>
+ <item>
+ <title>Long author feed item title.</title>
+ <link>http://example.com/long/author</link>
+ <author>I wanted to get out and walk eastward toward the park through the soft twilight, but each time I tried to go I became entangled in some wild, strident argument which pulled me back, as if with ropes, into my chair. Yet high over the city our line of yellow windows must have contributed their share of human secrecy to the casual watcher in the darkening streets, and I was him too, looking up and wondering. I was within and without, simultaneously enchanted and repelled by the inexhaustible variety of life.</author>
+ <description>Long author feed item description.</description>
+ </item>
+ </channel>
+</rss>