diff --git a/core/lib/Drupal/Core/Entity/Field/Type/UriItem.php b/core/lib/Drupal/Core/Entity/Field/Type/UriItem.php new file mode 100644 index 0000000..e5b84d4 --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Field/Type/UriItem.php @@ -0,0 +1,39 @@ + 'string', + 'label' => t('Text value'), + ); + } + return self::$propertyDefinitions; + } +} diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc index 3612e0c..90b1f9a 100644 --- a/core/modules/aggregator/aggregator.admin.inc +++ b/core/modules/aggregator/aggregator.admin.inc @@ -7,6 +7,7 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Drupal\aggregator\Plugin\FetcherManager; +use Drupal\aggregator\Plugin\Core\Entity\Feed; /** * Page callback: Displays the aggregator administration page. @@ -92,172 +93,31 @@ function aggregator_view() { } /** - * Form constructor for adding and editing feed sources. + * Page callback: Presents the aggregator feed creation form. * - * @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. + * @return array + * A form array as expected by drupal_render(). * - * @ingroup forms * @see aggregator_menu() - * @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' => 'url', - '#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 cron maintenance task.', 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 configure blocks 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')) { - // 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) { - // @todo Replicate this cache invalidation when these ops are separated. - // Invalidate the block cache to update aggregator feed-based derivatives. - if (module_exists('block')) { - drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); - } - 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']))); - } +function aggregator_feed_add() { + $feed = entity_create('aggregator_feed', array( + 'refresh' => 3600, + 'block' => 5, + )); + return entity_get_form($feed); } /** * Page callback: Deletes a feed. * - * @param $feed - * An associative array describing the feed to be cleared. + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed + * A feed object describing the feed to be cleared. * * @see aggregator_admin_remove_feed_submit() * @see aggregator_menu() */ -function aggregator_admin_remove_feed($form, $form_state, $feed) { +function aggregator_admin_remove_feed($form, $form_state, Feed $feed) { return confirm_form( array( 'feed' => array( @@ -265,7 +125,7 @@ function aggregator_admin_remove_feed($form, $form_state, $feed) { '#value' => $feed, ), ), - t('Are you sure you want to remove all items from the feed %feed?', array('%feed' => $feed->title)), + t('Are you sure you want to remove all items from the feed %feed?', array('%feed' => $feed->label())), 'admin/config/services/aggregator', t('This action cannot be undone.'), t('Remove items'), @@ -377,8 +237,6 @@ function aggregator_form_opml_submit($form, &$form_state) { return; } - $form_state['values']['op'] = t('Save'); - foreach ($feeds as $feed) { // Ensure URL is valid. if (!valid_url($feed['url'], TRUE)) { @@ -399,9 +257,14 @@ function aggregator_form_opml_submit($form, &$form_state) { } } - $form_state['values']['title'] = $feed['title']; - $form_state['values']['url'] = $feed['url']; - drupal_form_submit('aggregator_form_feed', $form_state); + $new_feed = entity_create('aggregator_feed', array( + 'title' => $feed['title'], + 'url' => $feed['url'], + 'refresh' => $form_state['values']['refresh'], + 'block' => $form_state['values']['block'], + )); + $new_feed->categories = $form_state['values']['category']; + $new_feed->save(); } $form_state['redirect'] = 'admin/config/services/aggregator'; @@ -443,17 +306,17 @@ function _aggregator_parse_opml($opml) { /** * Page callback: Refreshes a feed, then redirects to the overview page. * - * @param $feed + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed * An object describing the feed to be refreshed. * * @see aggregator_menu() */ -function aggregator_admin_refresh_feed($feed) { +function aggregator_admin_refresh_feed(Feed $feed) { // @todo CSRF tokens are validated in page callbacks rather than access // callbacks, because access callbacks are also invoked during menu link // generation. Add token support to routing: http://drupal.org/node/755584. $token = drupal_container()->get('request')->query->get('token'); - if (!isset($token) || !drupal_valid_token($token, 'aggregator/update/' . $feed->fid)) { + if (!isset($token) || !drupal_valid_token($token, 'aggregator/update/' . $feed->id())) { throw new AccessDeniedHttpException(); } @@ -590,7 +453,7 @@ function aggregator_admin_form_submit($form, &$form_state) { * Form constructor to add/edit/delete aggregator categories. * * @param $edit - * An associative array containing: + * An object containing: * - title: A string to use for the category title. * - description: A string to use for the category description. * - cid: The category ID. @@ -600,22 +463,22 @@ function aggregator_admin_form_submit($form, &$form_state) { * @see aggregator_form_category_validate() * @see aggregator_form_category_submit() */ -function aggregator_form_category($form, &$form_state, $edit = array('title' => '', 'description' => '', 'cid' => NULL)) { +function aggregator_form_category($form, &$form_state, $edit = NULL) { $form['title'] = array('#type' => 'textfield', '#title' => t('Title'), - '#default_value' => $edit['title'], + '#default_value' => isset($edit->title) ? $edit->title : '', '#maxlength' => 64, '#required' => TRUE, ); $form['description'] = array('#type' => 'textarea', '#title' => t('Description'), - '#default_value' => $edit['description'], + '#default_value' => isset($edit->description) ? $edit->description : '', ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save')); - if ($edit['cid']) { + if (!empty($edit->cid)) { $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete')); - $form['cid'] = array('#type' => 'hidden', '#value' => $edit['cid']); + $form['cid'] = array('#type' => 'hidden', '#value' => $edit->cid); } return $form; diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index 2fd5929..8cd7bb8 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -6,6 +6,7 @@ */ use Drupal\aggregator\Plugin\FetcherManager; +use Drupal\aggregator\Plugin\Core\Entity\Feed; /** * Denotes that a feed's items should never expire. @@ -51,17 +52,12 @@ function aggregator_help($path, $arg) { */ 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), + 'variables' => array('aggregator_feed' => NULL, 'view_mode' => NULL), 'file' => 'aggregator.pages.inc', 'template' => 'aggregator-feed-source', ), @@ -74,11 +70,11 @@ function aggregator_theme() { 'template' => 'aggregator-summary-items', ), 'aggregator_summary_item' => array( - 'variables' => array('item' => NULL), + 'variables' => array('aggregator_item' => NULL, 'view_mode' => NULL), 'file' => 'aggregator.pages.inc', ), 'aggregator_item' => array( - 'variables' => array('item' => NULL), + 'variables' => array('aggregator_item' => NULL, 'view_mode' => NULL), 'file' => 'aggregator.pages.inc', 'template' => 'aggregator-item', ), @@ -107,8 +103,7 @@ function aggregator_menu() { ); $items['admin/config/services/aggregator/add/feed'] = array( 'title' => 'Add feed', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('aggregator_form_feed'), + 'page callback' => 'aggregator_feed_add', 'access arguments' => array('administer news feeds'), 'type' => MENU_LOCAL_ACTION, 'file' => 'aggregator.admin.inc', @@ -221,6 +216,8 @@ function aggregator_menu() { 'file' => 'aggregator.admin.inc', ); $items['aggregator/sources/%aggregator_feed'] = array( + 'title callback' => 'entity_page_label', + 'title arguments' => array(2), 'page callback' => 'aggregator_page_source', 'page arguments' => array(2), 'access arguments' => array('access news feeds'), @@ -241,8 +238,8 @@ function aggregator_menu() { ); $items['aggregator/sources/%aggregator_feed/configure'] = array( 'title' => 'Configure', - 'page callback' => 'drupal_get_form', - 'page arguments' => array('aggregator_form_feed', 2), + 'page callback' => 'entity_get_form', + 'page arguments' => array(2), 'access arguments' => array('administer news feeds'), 'type' => MENU_LOCAL_TASK, 'weight' => 1, @@ -250,8 +247,8 @@ function aggregator_menu() { ); $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), + 'page callback' => 'entity_get_form', + 'page arguments' => array(6), 'access arguments' => array('administer news feeds'), 'file' => 'aggregator.admin.inc', ); @@ -276,7 +273,7 @@ function aggregator_menu() { * A string with the aggregator category title. */ function _aggregator_category_title($category) { - return $category['title']; + return $category->title; } /** @@ -310,17 +307,18 @@ function aggregator_permission() { * 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( + $result = db_query('SELECT fid FROM {aggregator_feed} WHERE queued = 0 AND checked + refresh < :time AND refresh <> :never', array( ':time' => REQUEST_TIME, ':never' => AGGREGATOR_CLEAR_NEVER )); $queue = queue('aggregator_feeds'); - foreach ($result as $feed) { + foreach ($result->fetchCol() as $fid) { + $feed = aggregator_feed_load($fid); 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) + ->condition('fid', $feed->id()) ->execute(); } } @@ -397,98 +395,18 @@ function aggregator_save_category($edit) { } /** - * Adds/edits/deletes an aggregator feed. - * - * @param $edit - * An associative array describing the feed to be added/edited/deleted. - * - * @return - * The ID of the feed. - */ -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')) { - foreach (entity_load_multiple_by_properties('block', array('plugin' => 'aggregator_feed_block:' . $edit['fid'])) as $block) { - $block->delete(); - } - } - } - 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(); - } - } - } - } - - return $edit['fid']; -} - -/** * Removes all items from a feed. * - * @param $feed + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed * An object describing the feed to be cleared. */ -function aggregator_remove($feed) { +function aggregator_remove(Feed $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) + ->condition('fid', $feed->id()) ->fields(array( 'checked' => 0, 'hash' => '', @@ -524,12 +442,12 @@ function _aggregator_get_variables() { /** * Checks a news feed for new items. * - * @param $feed + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed * An object describing the feed to be refreshed. */ -function aggregator_refresh($feed) { +function aggregator_refresh(Feed $feed) { // Store feed URL to track changes. - $feed_url = $feed->url; + $feed_url = $feed->url->value; list($fetcher, $parser, $processors) = _aggregator_get_variables(); @@ -547,30 +465,23 @@ function aggregator_refresh($feed) { // data. If both are equal we say that feed is not updated. $hash = hash('sha256', $feed->source_string); - if ($success && ($feed->hash != $hash)) { + if ($success && ($feed->hash->value != $hash)) { // Parse the feed. if (module_invoke($parser, 'aggregator_parse', $feed)) { + if (empty($feed->link->value)) { + $feed->link->value = $feed->url->value; + } + $feed->hash->value = $hash; // 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(); + $feed->save(); // 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)); + if ($feed->url->value != $feed_url) { + watchdog('aggregator', 'Updated URL for feed %title to %url.', array('%title' => $feed->label(), '%url' => $feed->url->value)); } - 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))); + watchdog('aggregator', 'There is new syndicated content from %site.', array('%site' => $feed->label())); + drupal_set_message(t('There is new syndicated content from %site.', array('%site' => $feed->label()))); // If there are items on the feed, let all enabled processors do their work on it. if (@count($feed->items)) { @@ -581,13 +492,13 @@ function aggregator_refresh($feed) { } } else { - drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed->title))); + drupal_set_message(t('There is no new syndicated content from %site.', array('%site' => $feed->label()))); } // 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) + ->condition('fid', $feed->id()) ->execute(); // Expire old feed items. @@ -599,19 +510,14 @@ function aggregator_refresh($feed) { /** * Loads an aggregator feed. * - * @param $fid + * @param int $fid * The feed id. * - * @return + * @return \Drupal\aggregator\Plugin\Core\Entity\Feed * 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]; + return entity_load('aggregator_feed', $fid); } /** @@ -626,7 +532,7 @@ function aggregator_feed_load($fid) { 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(); + $categories[$cid] = db_query('SELECT * FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $cid))->fetchObject(); } return $categories[$cid]; diff --git a/core/modules/aggregator/aggregator.pages.inc b/core/modules/aggregator/aggregator.pages.inc index a49a666..dc94336 100644 --- a/core/modules/aggregator/aggregator.pages.inc +++ b/core/modules/aggregator/aggregator.pages.inc @@ -5,6 +5,8 @@ * User page callbacks for the Aggregator module. */ +use Drupal\aggregator\Plugin\Core\Entity\Feed; + /** * Page callback: Displays the most recent items gathered from any feed. * @@ -24,7 +26,7 @@ function aggregator_page_last() { /** * Page callback: Displays all the items captured from the particular feed. * - * @param $feed + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed * The feed for which to display all items. * * @return string @@ -32,9 +34,8 @@ function aggregator_page_last() { * * @see aggregator_menu() */ -function aggregator_page_source($feed) { - drupal_set_title($feed->title); - $feed_source = theme('aggregator_feed_source', array('feed' => $feed)); +function aggregator_page_source(Feed $feed) { + $feed_source = entity_view($feed, 'default'); // It is safe to include the fid in the query because it's loaded from the // database by aggregator_feed_load(). @@ -73,7 +74,7 @@ function aggregator_page_source_form($form, $form_state, $feed) { * @ingroup forms */ function aggregator_page_category($category) { - drupal_add_feed('aggregator/rss/' . $category['cid'], config('system.site')->get('name') . ' ' . t('aggregator - @title', array('@title' => $category['title']))); + drupal_add_feed('aggregator/rss/' . $category->cid, config('system.site')->get('name') . ' ' . 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(). @@ -120,46 +121,38 @@ function aggregator_page_category_form($form, $form_state, $category) { * @return * An array of the feed items. */ -function aggregator_load_feed_items($type, $data = NULL) { +function aggregator_load_feed_items($type, $data = NULL, $limit = 20) { $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'); + $query->fields('i', array('iid')); break; case 'source': $query = db_select('aggregator_item', 'i'); $query - ->fields('i') - ->condition('i.fid', $data->fid); + ->fields('i', array('iid')) + ->condition('i.fid', $data->id()); 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'); + ->fields('i', array('iid')) + ->condition('cid', $data->cid); break; } $result = $query ->extend('Drupal\Core\Database\Query\PagerSelectExtender') - ->limit(20) + ->limit($limit) ->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; - } - + $items = entity_load_multiple('aggregator_item', $result->fetchCol()); return $items; } @@ -182,18 +175,22 @@ function aggregator_load_feed_items($type, $data = NULL) { 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); + $build = aggregator_categorize_items($items, $feed_source); } else { - // Assemble themed output. - $output = $feed_source; - foreach ($items as $item) { - $output .= theme('aggregator_item', array('item' => $item)); + // Assemble output. + $build = array( + '#type' => 'container', + '#attributes' => array('class' => array('aggregator-wrapper')), + ); + $build['feed_source'] = is_array($feed_source) ? $feed_source : array('#markup' => $feed_source); + if ($items) { + $build['items'] = entity_view_multiple($items, 'default'); + $build['pager']['#markup'] = theme('pager'); } - $output = theme('aggregator_wrapper', array('content' => $output)); } - return $output; + return $build; } /** @@ -220,13 +217,15 @@ function aggregator_categorize_items($items, $feed_source = '') { $categories = array(); $done = FALSE; $form['items'] = array(); + if ($items) { + $form['items'] = entity_view_multiple($items, 'default'); + } $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)); + $form['categories'][$item->id()] = 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->id())); $selected = array(); foreach ($categories_result as $category) { if (!$done) { @@ -237,7 +236,7 @@ function aggregator_categorize_items($items, $feed_source = '') { } } $done = TRUE; - $form['categories'][$item->iid] = array( + $form['categories'][$item->id()] = array( '#type' => config('aggregator.settings')->get('source.category_selector'), '#default_value' => $selected, '#options' => $categories, @@ -305,7 +304,8 @@ function theme_aggregator_categorize_items($variables) { $output .= drupal_render($form['submit']); $output .= drupal_render_children($form); - return theme('aggregator_wrapper', array('content' => $output)); + $output .= theme('pager'); + return $output; } /** @@ -324,38 +324,28 @@ function theme_aggregator_summary_item($variables) { } /** - * Processes variables for aggregator-wrapper.tpl.php. - * - * @see aggregator-wrapper.tpl.php - */ -function template_preprocess_aggregator_wrapper(&$variables) { - drupal_add_css(drupal_get_path('module', 'aggregator') . '/aggregator.theme.css'); - $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']; + $item = $variables['aggregator_item']; - $variables['feed_url'] = check_url($item->link); - $variables['feed_title'] = check_plain($item->title); - $variables['content'] = aggregator_filter_xss($item->description); + $variables['feed_url'] = check_url($item->link->value); + $variables['feed_title'] = check_plain($item->title->value); + $variables['content'] = aggregator_filter_xss($item->description->value); $variables['source_url'] = ''; $variables['source_title'] = ''; - if (isset($item->ftitle) && isset($item->fid)) { - $variables['source_url'] = url("aggregator/sources/$item->fid"); + if (isset($item->ftitle) && isset($item->fid->value)) { + $variables['source_url'] = url("aggregator/sources/$item->fid->value"); $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))); + if (date('Ymd', $item->timestamp->value) == date('Ymd')) { + $variables['source_date'] = t('%ago ago', array('%ago' => format_interval(REQUEST_TIME - $item->timestamp->value))); } else { - $variables['source_date'] = format_date($item->timestamp, 'medium'); + $variables['source_date'] = format_date($item->timestamp->value, 'medium'); } $variables['categories'] = array(); @@ -373,25 +363,35 @@ function template_preprocess_aggregator_item(&$variables) { * @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'); + $feeds = entity_load_multiple('aggregator_feed'); - $output = ''; - foreach ($result as $feed) { + $build = array( + '#type' => 'container', + '#attributes' => array('class' => array('aggregator-wrapper')), + '#sorted' => TRUE, + ); + foreach ($feeds as $feed) { // Most recent items: $summary_items = array(); $aggregator_summary_items = config('aggregator.settings')->get('source.list_max'); if ($aggregator_summary_items) { - $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, $aggregator_summary_items, array(':fid' => $feed->fid)); - foreach ($items as $item) { - $summary_items[] = theme('aggregator_summary_item', array('item' => $item)); + if ($items = aggregator_load_feed_items('source', $feed, $aggregator_summary_items)) { + $summary_items = entity_view_multiple($items, 'summary'); } } - $feed->url = url('aggregator/sources/' . $feed->fid); - $output .= theme('aggregator_summary_items', array('summary_items' => $summary_items, 'source' => $feed)); + $feed->url = url('aggregator/sources/' . $feed->id()); + $build[$feed->id()] = array( + '#theme' => 'aggregator_summary_items', + '#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)); + $build['feed_icon'] = array( + '#theme' => 'feed_icon', + '#url' => 'aggregator/opml', + '#title' => t('OPML feed'), + ); + return $build; } /** @@ -405,21 +405,28 @@ function aggregator_page_sources() { 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 = ''; + $build = array( + '#type' => 'container', + '#attributes' => array('class' => array('aggregator-wrapper')), + '#sorted' => TRUE, + ); + $aggregator_summary_items = config('aggregator.settings')->get('source.list_max'); foreach ($result as $category) { - $aggregator_summary_items = config('aggregator.settings')->get('source.list_max'); + $summary_items = array(); if ($aggregator_summary_items) { - $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, $aggregator_summary_items, array(':cid' => $category->cid)); - foreach ($items as $item) { - $summary_items[] = theme('aggregator_summary_item', array('item' => $item)); + if ($items = aggregator_load_feed_items('category', $category, $aggregator_summary_items)) { + $summary_items = entity_view_multiple($items, 'summary'); } } $category->url = url('aggregator/categories/' . $category->cid); - $output .= theme('aggregator_summary_items', array('summary_items' => $summary_items, 'source' => $category)); + $build[$category->cid] = array( + '#theme' => 'aggregator_summary_items', + '#summary_items' => $summary_items, + '#source' => $category, + ); } - return theme('aggregator_wrapper', array('content' => $output)); + return $build; } /** @@ -556,9 +563,16 @@ function theme_aggregator_page_opml($variables) { * @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; + $variables['title'] = check_plain($variables['source']->label()); + $summary_items = array(); + foreach (element_children($variables['summary_items']) as $key) { + $summary_items[] = $variables['summary_items'][$key]; + } + $variables['summary_list'] = array( + '#theme' => 'item_list', + '#items' => $summary_items, + ); + $variables['source_url'] = $variables['source']->url->value; } /** @@ -567,19 +581,19 @@ function template_preprocess_aggregator_summary_items(&$variables) { * @see aggregator-summary-item.tpl.php */ function template_preprocess_aggregator_summary_item(&$variables) { - $item = $variables['item']; + $item = $variables['aggregator_item']; - $variables['item_url'] = l(check_plain($item->title), check_url(url($item->link, array('absolute' => TRUE))), array( + $variables['item_url'] = l(check_plain($item->label()), check_url(url($item->link->value, array('absolute' => TRUE))), array( 'attributes' => array( 'class' => array('feed-item-url',), ), )); $variables['item_age'] = theme('datetime', array( 'attributes' => array( - 'datetime' => format_date($item->timestamp, 'html_datetime', '', 'UTC'), + 'datetime' => format_date($item->timestamp->value, 'html_datetime', '', 'UTC'), 'class' => array('feed-item-age',), ), - 'text' => t('%age old', array('%age' => format_interval(REQUEST_TIME - $item->timestamp))), + 'text' => t('%age old', array('%age' => format_interval(REQUEST_TIME - $item->timestamp->value))), 'html' => TRUE, )); } @@ -590,22 +604,21 @@ function template_preprocess_aggregator_summary_item(&$variables) { * @see aggregator-feed-source.tpl.php */ function template_preprocess_aggregator_feed_source(&$variables) { - $feed = $variables['feed']; + $feed = $variables['aggregator_feed']; - $variables['source_icon'] = theme('feed_icon', array('url' => $feed->url, 'title' => t('!title feed', array('!title' => $feed->title)))); + $variables['source_icon'] = theme('feed_icon', array('url' => $feed->url->value, 'title' => t('!title feed', array('!title' => $feed->label())))); - 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'))); + if (!empty($feed->image->value) && $feed->label() && !empty($feed->link->value)) { + $variables['source_image'] = l(theme('image', array('path' => $feed->image->value, 'alt' => $feed->title->value)), $feed->link->value, 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))); + $variables['source_description'] = aggregator_filter_xss($feed->description->value); + $variables['source_url'] = check_url(url($feed->link->value, array('absolute' => TRUE))); if ($feed->checked) { - $variables['last_checked'] = t('@time ago', array('@time' => format_interval(REQUEST_TIME - $feed->checked))); + $variables['last_checked'] = t('@time ago', array('@time' => format_interval(REQUEST_TIME - $feed->checked->value))); } else { $variables['last_checked'] = t('never'); diff --git a/core/modules/aggregator/aggregator.parser.inc b/core/modules/aggregator/aggregator.parser.inc index bfa48f2..3b906e8 100644 --- a/core/modules/aggregator/aggregator.parser.inc +++ b/core/modules/aggregator/aggregator.parser.inc @@ -5,6 +5,8 @@ * Parser functions for the aggregator module. */ +use Drupal\aggregator\Plugin\Core\Entity\Feed; + /** * Implements hook_aggregator_parse_info(). */ @@ -18,7 +20,7 @@ function aggregator_aggregator_parse_info() { /** * Implements hook_aggregator_parse(). */ -function aggregator_aggregator_parse($feed) { +function aggregator_aggregator_parse(Feed $feed) { global $channel, $image; // Filter the input data. @@ -35,9 +37,9 @@ function aggregator_aggregator_parse($feed) { } // 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->link->value = !empty($channel['link']) ? $channel['link'] : ''; + $feed->description->value = !empty($channel['description']) ? $channel['description'] : ''; + $feed->image->value = !empty($image['url']) ? $image['url'] : ''; // Clear the page and block caches. cache_invalidate_tags(array('content' => TRUE)); @@ -51,15 +53,15 @@ function aggregator_aggregator_parse($feed) { /** * Parses a feed and stores its items. * - * @param $data + * @param string $data * The feed data. - * @param $feed + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed * An object describing the feed to be parsed. * * @return * FALSE on error, TRUE otherwise. */ -function aggregator_parse_feed(&$data, $feed) { +function aggregator_parse_feed(&$data, Feed $feed) { global $items, $image, $channel; // Unset the global variables before we use them. @@ -74,8 +76,8 @@ function aggregator_parse_feed(&$data, $feed) { 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'); + watchdog('aggregator', 'The feed from %site seems to be broken due to an error "%error" on line %line.', array('%site' => $feed->label(), '%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->label(), '%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); @@ -111,7 +113,7 @@ function aggregator_parse_feed(&$data, $feed) { $item['link'] = $item['link']; } else { - $item['link'] = $feed->link; + $item['link'] = $feed->link->value; } // Atom feeds have an ID tag instead of a GUID tag. diff --git a/core/modules/aggregator/aggregator.processor.inc b/core/modules/aggregator/aggregator.processor.inc index f585a3c..c02654c 100644 --- a/core/modules/aggregator/aggregator.processor.inc +++ b/core/modules/aggregator/aggregator.processor.inc @@ -5,6 +5,8 @@ * Processor functions for the aggregator module. */ +use Drupal\aggregator\Plugin\Core\Entity\Feed; + /** * Implements hook_aggregator_process_info(). */ @@ -22,26 +24,46 @@ function aggregator_aggregator_process($feed) { if (is_object($feed)) { if (is_array($feed->items)) { foreach ($feed->items as $item) { + // @todo: The default entity render controller always returns an empty + // array, which is ignored in aggregator_save_item() currently. Should + // probably be fixed. + if (empty($item['title'])) { + continue; + } + // 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(); + $values = array('fid' => $feed->id(), 'guid' => $item['guid']); } 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(); + $values = array('fid' => $feed->id(), 'link' => $item['link']); } else { - $entry = db_query("SELECT iid, timestamp FROM {aggregator_item} WHERE fid = :fid AND title = :title", array(':fid' => $feed->fid, ':title' => $item['title']))->fetchObject(); + $values = array('fid' => $feed->id(), 'title' => $item['title']); } - if (!$item['timestamp']) { - $item['timestamp'] = isset($entry->timestamp) ? $entry->timestamp : REQUEST_TIME; + + // Try to load an existing entry. + if ($entry = entity_load_multiple_by_properties('aggregator_item', $values)) { + $entry = reset($entry); + } + else { + $entry = entity_create('aggregator_item', array()); + } + if ($item['timestamp']) { + $entry->timestamp->value = $item['timestamp']; } // 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'])); + $entry->title->value = truncate_utf8($item['title'], 255, TRUE, TRUE); + $entry->author->value = truncate_utf8($item['author'], 255, TRUE, TRUE); + + $entry->fid->value = $feed->id(); + $entry->link->value = $item['link']; + $entry->description->value = $item['description']; + $entry->guid->value = $item['guid']; + $entry->save(); } } } @@ -51,17 +73,10 @@ function aggregator_aggregator_process($feed) { * 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(); + $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchCol(); + entity_delete_multiple('aggregator_item', $iids); - drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed->title))); + drupal_set_message(t('The news items from %site have been removed.', array('%site' => $feed->label()))); } /** @@ -145,71 +160,24 @@ function _aggregator_characters($length) { } /** - * 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 + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed * Object describing feed. */ -function aggregator_expire($feed) { +function aggregator_expire(Feed $feed) { $aggregator_clear = config('aggregator.settings')->get('items.expire'); 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, + ':fid' => $feed->id(), ':timestamp' => $age, )) ->fetchCol(); if ($iids) { - db_delete('aggregator_category_item') - ->condition('iid', $iids, 'IN') - ->execute(); - db_delete('aggregator_item') - ->condition('iid', $iids, 'IN') - ->execute(); + entity_delete_multiple('aggregator_item', $iids); } } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php new file mode 100644 index 0000000..12a156b --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedFormController.php @@ -0,0 +1,145 @@ + 'textfield', + '#title' => t('Title'), + '#default_value' => $feed->label(), + '#maxlength' => 255, + '#description' => t('The name of the feed (or the name of the website providing the feed).'), + '#required' => TRUE, + ); + $form['url'] = array( + '#type' => 'url', + '#title' => t('URL'), + '#default_value' => $feed->url->value, + '#maxlength' => NULL, + '#description' => t('The fully-qualified URL of the feed.'), + '#required' => TRUE, + ); + $form['refresh'] = array('#type' => 'select', + '#title' => t('Update interval'), + '#default_value' => $feed->refresh->value, + '#options' => $period, + '#description' => t('The length of time between feed updates. Requires a correctly configured cron maintenance task.', array('@cron' => url('admin/reports/status'))), + ); + $form['block'] = array('#type' => 'select', + '#title' => t('News items in block'), + '#default_value' => $feed->block->value, + '#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 configure blocks 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 FROM {aggregator_category} c ORDER BY title'); + foreach ($categories as $category) { + $options[$category->cid] = check_plain($category->title); + if (!empty($feed->categories) && in_array($category->cid, array_keys($feed->categories))) { + $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.'), + ); + } + + return parent::form($form, $form_state, $feed); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::validate(). + */ + public function validate(array $form, array &$form_state) { + $feed = $this->buildEntity($form, $form_state); + // Check for duplicate titles. + if ($feed->id()) { + $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE (title = :title OR url = :url) AND fid <> :fid", array(':title' => $feed->label(), ':url' => $feed->url->value, ':fid' => $feed->id())); + } + else { + $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = :title OR url = :url", array(':title' => $feed->label(), ':url' => $feed->url->value)); + } + + foreach ($result as $item) { + if (strcasecmp($item->title, $feed->label()) == 0) { + form_set_error('title', t('A feed named %feed already exists. Enter a unique title.', array('%feed' => $feed->label()))); + } + if (strcasecmp($item->url, $feed->url->value) == 0) { + form_set_error('url', t('A feed with this URL %url already exists. Enter a unique URL.', array('%url' => $feed->url->value))); + } + } + parent::validate($form, $form_state); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::save(). + */ + public function save(array $form, array &$form_state) { + $feed = $this->getEntity($form_state); + $insert = (bool) $feed->id(); + if (!empty($form_state['values']['category'])) { + // Store category values for post save operations. + // @see Drupal\Core\Entity\FeedStorageController::postSave() + $feed->categories = $form_state['values']['category']; + } + $feed->save(); + if ($insert) { + drupal_set_message(t('The feed %feed has been updated.', array('%feed' => $feed->label()))); + if (arg(0) == 'admin') { + $form_state['redirect'] = 'admin/config/services/aggregator'; + } + else { + $form_state['redirect'] = 'aggregator/sources/' . $feed->id(); + } + } + else { + watchdog('aggregator', 'Feed %feed added.', array('%feed' => $feed->label()), WATCHDOG_NOTICE, l(t('view'), 'admin/config/services/aggregator')); + drupal_set_message(t('The feed %feed has been added.', array('%feed' => $feed->label()))); + } + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::delete(). + */ + public function delete(array $form, array &$form_state) { + $feed = $this->getEntity($form_state); + $feed->delete(); + watchdog('aggregator', 'Feed %feed deleted.', array('%feed' => $feed->label())); + drupal_set_message(t('The feed %feed has been deleted.', array('%feed' => $feed->label()))); + if (arg(0) == 'admin') { + $form_state['redirect'] = 'admin/config/services/aggregator'; + } + else { + $form_state['redirect'] = 'aggregator/sources'; + } + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedRenderController.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedRenderController.php new file mode 100644 index 0000000..47de47b --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedRenderController.php @@ -0,0 +1,27 @@ + '', + 'description' => '', + 'image' => '', + ); + return parent::create($values); + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::attachLoad(). + */ + protected function attachLoad(&$queried_entities, $load_revision = FALSE) { + parent::attachLoad($queried_entities, $load_revision); + foreach ($queried_entities as $item) { + $item->categories = db_query('SELECT c.cid, c.title FROM {aggregator_category} c JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = :fid ORDER BY title', array(':fid' => $item->id()))->fetchAllKeyed(); + } + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::preDelete(). + */ + protected function preDelete($entities) { + parent::preDelete($entities); + + // Invalidate the block cache to update aggregator feed-based derivatives. + if (module_exists('block')) { + drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); + } + foreach ($entities as $entity) { + $iids = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $entity->id()))->fetchCol(); + if ($iids) { + entity_delete_multiple('aggregator_item', $iids); + } + } + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::postDelete(). + */ + protected function postDelete($entities) { + parent::postDelete($entities); + + foreach ($entities as $entity) { + // Make sure there is no active block for this feed. + $block_configs = config_get_storage_names_with_prefix('plugin.core.block'); + foreach ($block_configs as $config_id) { + $config = config($config_id); + if ($config->get('id') == 'aggregator_feed_block:' . $entity->id()) { + $config->delete(); + } + } + } + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::preSave(). + */ + protected function preSave(EntityInterface $entity) { + parent::preSave($entity); + + // Invalidate the block cache to update aggregator feed-based derivatives. + if (module_exists('block')) { + drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); + } + // An existing feed is being modified, delete the category listings. + db_delete('aggregator_category_feed') + ->condition('fid', $entity->id()) + ->execute(); + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::postSave(). + */ + protected function postSave(EntityInterface $entity, $update) { + parent::postSave($entity, $update); + + if (!empty($entity->categories)) { + foreach ($entity->categories as $cid => $value) { + if ($value) { + db_insert('aggregator_category_feed') + ->fields(array( + 'fid' => $entity->id(), + 'cid' => $cid, + )) + ->execute(); + } + } + } + } + + /** + * Implements Drupal\Core\Entity\DataBaseStorageControllerNG::baseFieldDefinitions(). + */ + public function baseFieldDefinitions() { + $fields['fid'] = array( + 'label' => t('ID'), + 'description' => t('The ID of the aggregor feed.'), + 'type' => 'integer_field', + 'read-only' => TRUE, + ); + $fields['langcode'] = array( + 'label' => t('Language code'), + 'description' => t('The language code of the aggregator feed.'), + 'type' => 'language_field', + ); + $fields['title'] = array( + 'label' => t('Title'), + 'description' => t('The title of the feed.'), + 'type' => 'string_field', + ); + $fields['url'] = array( + 'label' => t('URL'), + 'description' => t('The URL to the feed.'), + 'type' => 'uri_field', + ); + $fields['refresh'] = array( + 'label' => t('Refresh'), + 'description' => t('How often to check for new feed items, in seconds.'), + 'type' => 'integer_field', + ); + $fields['checked'] = array( + 'label' => t('Checked'), + 'description' => t('Last time feed was checked for new items, as Unix timestamp.'), + 'type' => 'integer_field', + ); + $fields['queued'] = array( + 'label' => t('Queued'), + 'description' => t('Time when this feed was queued for refresh, 0 if not queued.'), + 'type' => 'integer_field', + ); + $fields['link'] = array( + 'label' => t('Link'), + 'description' => t('The link of the feed.'), + 'type' => 'uri_field', + ); + $fields['description'] = array( + 'label' => t('Description'), + 'description' => t("The parent website's description that comes from the element in the feed."), + 'type' => 'string_field', + ); + $fields['image'] = array( + 'label' => t('image'), + 'description' => t('An image representing the feed.'), + 'type' => 'uri_field', + ); + $fields['hash'] = array( + 'label' => t('Hash'), + 'description' => t('Calculated hash of the feed data, used for validating cache.'), + 'type' => 'string_field', + ); + $fields['etag'] = array( + 'label' => t('Etag'), + 'description' => t('Entity tag HTTP response header, used for validating cache.'), + 'type' => 'string_field', + ); + $fields['modified'] = array( + 'label' => t('Modified'), + 'description' => t('When the feed was last modified, as a Unix timestamp.'), + 'type' => 'integer_field', + ); + $fields['block'] = array( + 'label' => t('Block'), + 'description' => t('Number of items to display in the feed’s block.'), + 'type' => 'integer_field', + ); + return $fields; + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/ItemRenderController.php b/core/modules/aggregator/lib/Drupal/aggregator/ItemRenderController.php new file mode 100644 index 0000000..af11005 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/ItemRenderController.php @@ -0,0 +1,31 @@ +timestamp->value = REQUEST_TIME; + return $entity; + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::attachLoad(). + */ + protected function attachLoad(&$queried_entities, $load_revision = FALSE) { + parent::attachLoad($queried_entities, $load_revision); + foreach ($queried_entities 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->id()))->fetchAll(); + } + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::preDelete(). + */ + protected function preDelete($entities) { + parent::preDelete($entities); + + db_delete('aggregator_category_item') + ->condition('iid', array_keys($entities), 'IN') + ->execute(); + } + + /** + * Overrides Drupal\Core\Entity\DataBaseStorageController::postSave(). + */ + protected function postSave(EntityInterface $entity, $update) { + parent::postSave($entity, $update); + + $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $entity->fid->value)); + foreach ($result as $category) { + db_merge('aggregator_category_item') + ->key(array( + 'iid' => $entity->id(), + 'cid' => $category->cid, + )) + ->execute(); + } + } + + /** + * Implements Drupal\Core\Entity\DataBaseStorageControllerNG::baseFieldDefinitions(). + */ + public function baseFieldDefinitions() { + $fields['iid'] = array( + 'label' => t('ID'), + 'description' => t('The ID of the aggregor item.'), + 'type' => 'integer_field', + 'read-only' => TRUE, + ); + $fields['fid'] = array( + 'label' => t('Aggregator feed ID'), + 'description' => t('The ID of the aggregator feed.'), + 'type' => 'integer_field', + ); + $fields['title'] = array( + 'label' => t('Title'), + 'description' => t('The title of the feed item.'), + 'type' => 'string_field', + ); + $fields['link'] = array( + 'label' => t('Link'), + 'description' => t('The link of the feed item.'), + 'type' => 'uri_field', + ); + $fields['author'] = array( + 'label' => t('Author'), + 'description' => t('The author of the feed item.'), + 'type' => 'string_field', + ); + $fields['description'] = array( + 'label' => t('Description'), + 'description' => t('The body of the feed item.'), + 'type' => 'string_field', + ); + $fields['timestamp'] = array( + 'label' => t('Posted timestamp'), + 'description' => t('Posted date of the feed item, as a Unix timestamp.'), + 'type' => 'integer_field', + ); + $fields['guid'] = array( + 'label' => t('GUID'), + 'description' => t('Unique identifier for the feed item.'), + 'type' => 'string_field', + ); + return $fields; + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Feed.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Feed.php new file mode 100644 index 0000000..c53558e --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Feed.php @@ -0,0 +1,167 @@ + element in the feed. + * + * @var \Drupal\Core\Entity\Field\FieldInterface + */ + public $link ; + + /** + * The parent website's description; + * comes from the element in the feed. + * + * @var \Drupal\Core\Entity\Field\FieldInterface + */ + public $description; + + /** + * An image representing the feed. + * + * @var \Drupal\Core\Entity\Field\FieldInterface + */ + public $image; + + /** + * Calculated hash of the feed data, used for validating cache. + * + * @var \Drupal\Core\Entity\Field\FieldInterface + */ + public $hash; + + /** + * Entity tag HTTP response header, used for validating cache. + * + * @var \Drupal\Core\Entity\Field\FieldInterface + */ + public $etag; + + /** + * When the feed was last modified, as a Unix timestamp. + * + * @var \Drupal\Core\Entity\Field\FieldInterface + */ + public $modified; + + /** + * Number of items to display in the feed’s block. + * + * @var \Drupal\Core\Entity\Field\FieldInterface + */ + public $block; + + /** + * Overrides Drupal\Core\Entity\EntityNG::init(). + */ + public function init() { + parent::init(); + + // We unset all defined properties, so magic getters apply. + unset($this->fid); + unset($this->title); + unset($this->url); + unset($this->refresh); + unset($this->checked); + unset($this->queued); + unset($this->link); + unset($this->description); + unset($this->image); + unset($this->hash); + unset($this->etag); + unset($this->modified); + unset($this->block); + + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::id(). + */ + public function id() { + return $this->get('fid')->value; + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::label(). + */ + public function label($langcode = NULL) { + return $this->get('title')->value; + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php new file mode 100644 index 0000000..cc780bd --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php @@ -0,0 +1,124 @@ +iid); + unset($this->fid); + unset($this->title); + unset($this->author); + unset($this->description); + unset($this->guid); + unset($this->link); + unset($this->timestamp); + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::id(). + */ + public function id() { + return $this->get('iid')->value; + } + + /** + * Implements Drupal\Core\Entity\EntityInterface::label(). + */ + public function label($langcode = NULL) { + return $this->get('title')->value; + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherInterface.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherInterface.php index 6c79a59..120f596 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherInterface.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/FetcherInterface.php @@ -7,6 +7,8 @@ namespace Drupal\aggregator\Plugin; +use Drupal\aggregator\Plugin\Core\Entity\Feed; + /** * Defines an interface for aggregator fetcher implementations. * @@ -21,14 +23,15 @@ /** * Downloads feed data. * - * @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 + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed + * A feed object representing the resource to be downloaded. + * $feed->url->value 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. */ - public function fetch(&$feed); + public function fetch(Feed $feed); } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php index 2df801a..4775873 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/aggregator/fetcher/DefaultFetcher.php @@ -8,6 +8,7 @@ namespace Drupal\aggregator\Plugin\aggregator\fetcher; use Drupal\aggregator\Plugin\FetcherInterface; +use Drupal\aggregator\Plugin\Core\Entity\Feed; use Drupal\Core\Annotation\Plugin; use Drupal\Core\Annotation\Translation; use Guzzle\Http\Exception\BadResponseException; @@ -28,16 +29,16 @@ class DefaultFetcher implements FetcherInterface { /** * Implements Drupal\aggregator\Plugin\FetcherInterface::fetch(). */ - function fetch(&$feed) { - $request = drupal_container()->get('http_default_client')->get($feed->url); + function fetch(Feed $feed) { + $request = drupal_container()->get('http_default_client')->get($feed->url->value); $feed->source_string = FALSE; // Generate conditional GET headers. - if ($feed->etag) { - $request->addHeader('If-None-Match', $feed->etag); + if ($feed->etag->value) { + $request->addHeader('If-None-Match', $feed->etag->value); } - if ($feed->modified) { - $request->addHeader('If-Modified-Since', gmdate(DATE_RFC1123, $feed->modified)); + if ($feed->modified->value) { + $request->addHeader('If-Modified-Since', gmdate(DATE_RFC1123, $feed->modified->value)); } try { @@ -51,8 +52,8 @@ function fetch(&$feed) { } catch (BadResponseException $e) { $response = $e->getResponse(); - watchdog('aggregator', 'The feed from %site seems to be broken due to "%error".', array('%site' => $feed->title, '%error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()), WATCHDOG_WARNING); - drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->title, '%error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()))); + watchdog('aggregator', 'The feed from %site seems to be broken due to "%error".', array('%site' => $feed->label(), '%error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()), WATCHDOG_WARNING); + drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->label(), '%error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()))); return FALSE; } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AddFeedTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AddFeedTest.php index 9f6fc24..6d27c91 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AddFeedTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AddFeedTest.php @@ -27,14 +27,15 @@ function testAddFeed() { // 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.'); + $this->assertTrue($this->uniqueFeed($feed->label(), $feed->url->value), 'The feed is unique.'); // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->drupalGet('aggregator/sources/' . $feed->id()); $this->assertResponse(200, 'Feed source exists.'); - $this->assertText($feed->title, 'Page title'); - $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize'); + $this->assertText($feed->label(), 'Page title'); + $this->drupalGet('aggregator/sources/' . $feed->id() . '/categorize'); $this->assertResponse(200, 'Feed categorization page exists.'); + $this->assertText($feed->label()); // Delete feed. $this->deleteFeed($feed); @@ -54,15 +55,16 @@ function testAddLongFeed() { $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.'); + $this->assertTrue($this->uniqueFeed($feed->label(), $feed->url->value), 'The first long URL feed is unique.'); + $this->assertTrue($this->uniqueFeed($feed_2->label(), $feed_2->url->value), 'The second long URL feed is unique.'); // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->drupalGet('aggregator/sources/' . $feed->id()); $this->assertResponse(200, 'Long URL feed source exists.'); - $this->assertText($feed->title, 'Page title'); - $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize'); + $this->assertText($feed->label(), 'Page title'); + $this->drupalGet('aggregator/sources/' . $feed->id() . '/categorize'); $this->assertResponse(200, 'Long URL feed categorization page exists.'); + $this->assertText($feed->label()); // Delete feeds. $this->deleteFeed($feed); diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorCronTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorCronTest.php index 94e113e..e9aac8c 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorCronTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorCronTest.php @@ -29,29 +29,29 @@ public function testCron() { $this->createSampleNodes(); $feed = $this->createFeed(); $this->cronRun(); - $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->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->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->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(), 'Expected number of items in database.'); $this->cronRun(); - $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->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->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) + ->condition('fid', $feed->id()) ->fields(array( 'queued' => REQUEST_TIME, )) ->execute(); $this->cronRun(); - $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->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(), 'Expected number of items in database.'); db_update('aggregator_feed') - ->condition('fid', $feed->fid) + ->condition('fid', $feed->id()) ->fields(array( 'queued' => 0, )) ->execute(); $this->cronRun(); - $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->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(), 'Expected number of items in database.'); } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php index e1f8fcd..464c4f2 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php @@ -50,26 +50,26 @@ public function testBlockLinks() { )); $this->drupalLogin($admin_user); - $block = $this->drupalPlaceBlock("aggregator_feed_block:{$feed->fid}", array('label' => 'feed-' . $feed->title), array('block_count' => 2)); + $block = $this->drupalPlaceBlock("aggregator_feed_block:{$feed->id()}", array('label' => 'feed-' . $feed->label()), array('block_count' => 2)); // Confirm that the block is now being displayed on pages. $this->drupalGet('node'); $this->assertText($block->label(), 'Feed block is displayed on the page.'); // Find the expected read_more link. - $href = 'aggregator/sources/' . $feed->fid; + $href = 'aggregator/sources/' . $feed->id(); $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)); + $correct_titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => $feed->label())); $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); + $feed->save(); // Check that the block is no longer displayed. $this->drupalGet('node'); $this->assertNoText($block->label(), 'Feed block is not displayed on the page when number of items is set to 0.'); @@ -91,7 +91,7 @@ public function testFeedPage() { $this->updateFeedItems($feed, 30); // Check for the presence of a pager. - $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->drupalGet('aggregator/sources/' . $feed->id()); $elements = $this->xpath("//ul[@class=:class]", array(':class' => 'pager')); $this->assertTrue(!empty($elements), 'Individual source page contains a pager.'); diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php index faf5627..d5fc747 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php @@ -8,6 +8,7 @@ namespace Drupal\aggregator\Tests; use Drupal\simpletest\WebTestBase; +use Drupal\aggregator\Plugin\Core\Entity\Feed; /** * Defines a base class for testing the Aggregator module. @@ -43,7 +44,7 @@ function setUp() { * (optional) If given, feed will be created with this URL, otherwise * /rss.xml will be used. Defaults to NULL. * - * @return $feed + * @return \Drupal\aggregator\Plugin\Core\Entity\Feed $feed * Full feed object if possible. * * @see getFeedEditArray() @@ -53,20 +54,20 @@ function createFeed($feed_url = NULL) { $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; + $fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title'], ':url' => $edit['url']))->fetchField(); + $this->assertTrue(!empty($fid), 'The feed found in database.'); + return aggregator_feed_load($fid); } /** * Deletes an aggregator feed. * - * @param $feed + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $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.'); + function deleteFeed(Feed $feed) { + $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->id(), array(), t('Delete')); + $this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->label())), 'Feed deleted successfully.'); } /** @@ -96,6 +97,34 @@ function getFeedEditArray($feed_url = NULL) { } /** + * Returns a randomly generated feed edit object. + * + * @param string $feed_url + * (optional) If given, feed will be created with this URL, otherwise + * /rss.xml will be used. Defaults to NULL. + * @param array $values + * (optional) Default values to initialize object properties with. + * + * @return \Drupal\aggregator\Plugin\Core\Entity\Feed + * A feed object. + */ + function getFeedEditObject($feed_url = NULL, array $values = array()) { + $feed_name = $this->randomName(10); + if (!$feed_url) { + $feed_url = url('rss.xml', array( + 'query' => array('feed' => $feed_name), + 'absolute' => TRUE, + )); + } + $values += array( + 'title' => $feed_name, + 'url' => $feed_url, + 'refresh' => '900', + ); + return entity_create('aggregator_feed', $values); + } + + /** * Returns the count of the randomly created feed array. * * @return @@ -113,18 +142,18 @@ function getDefaultFeedItemCount() { * This method simulates a click to * admin/config/services/aggregator/update/$fid. * - * @param $feed - * Feed object representing the feed, passed by reference. + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed + * Feed object representing the feed. * @param $expected_count * Expected number of feed items. */ - function updateFeedItems(&$feed, $expected_count) { + function updateFeedItems(Feed $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))); + $this->drupalGet($feed->url->value); + $this->assertResponse(200, format_string('!url is reachable.', array('!url' => $feed->url->value))); // Attempt to access the update link directly without an access token. - $this->drupalGet('admin/config/services/aggregator/update/' . $feed->fid); + $this->drupalGet('admin/config/services/aggregator/update/' . $feed->id()); $this->assertResponse(403); // Refresh the feed (simulated link click). @@ -132,7 +161,7 @@ function updateFeedItems(&$feed, $expected_count) { $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)); + $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id())); $items = array(); $feed->items = array(); foreach ($result as $item) { @@ -145,40 +174,41 @@ function updateFeedItems(&$feed, $expected_count) { /** * Confirms an item removal from a feed. * - * @param $feed + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $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.'); + function removeFeedItems(Feed $feed) { + $this->drupalPost('admin/config/services/aggregator/remove/' . $feed->id(), array(), t('Remove items')); + $this->assertRaw(t('The news items from %title have been removed.', array('%title' => $feed->label())), 'Feed items removed.'); } /** * Adds and removes feed items and ensure that the count is zero. * - * @param $feed + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed * Feed object representing the feed. - * @param $expected_count + * @param int $expected_count * Expected number of feed items. */ - function updateAndRemove($feed, $expected_count) { + function updateAndRemove(Feed $feed, $expected_count) { $this->updateFeedItems($feed, $expected_count); - $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(); $this->assertTrue($count); $this->removeFeedItems($feed); - $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(); $this->assertTrue($count == 0); } /** * Pulls feed categories from {aggregator_category_feed} table. * - * @param $feed + * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed * Feed object representing the feed. */ - function getFeedCategories($feed) { + function getFeedCategories(Feed $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)); + $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $feed->id())); + foreach ($result as $category) { $feed->categories[] = $category->cid; } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php index a6a1dab..1853ff1 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php @@ -44,9 +44,10 @@ function testCategorizeFeedItem() { db_insert('aggregator_category_feed') ->fields(array( 'cid' => $category->cid, - 'fid' => $feed->fid, + 'fid' => $feed->id(), )) ->execute(); + $this->updateFeedItems($feed, $this->getDefaultFeedItemCount()); $this->getFeedCategories($feed); $this->assertTrue(!empty($feed->categories), 'The category found in the feed.'); diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedTest.php index 9636d55..ba360a8 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedTest.php @@ -37,18 +37,16 @@ function testCategorizeFeed() { $categories = $this->getCategories(); // Create a feed and assign 2 categories to it. - $feed = $this->getFeedEditArray(); - $feed['block'] = 5; + $feed = $this->getFeedEditObject(NULL, array('block' => 5)); foreach ($categories as $cid => $category) { - $feed['category'][$cid] = $cid; + $feed->categories[$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(); + $feed->save(); + $db_fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed->label(), ':url' => $feed->url->value))->fetchField(); + $db_feed = aggregator_feed_load($db_fid); // Assert the feed has two categories. - $this->getFeedCategories($db_feed); $this->assertEqual(count($db_feed->categories), 2, 'Feed has 2 categories'); } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php index ac8c5fa..f2e1cf0 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php @@ -33,8 +33,8 @@ function setUp() { 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->drupalGet('aggregator/sources/' . $feed->id()); + $this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label()))); $this->assertText('First example feed item title'); $this->assertLinkByHref('http://example.com/example-turns-one'); $this->assertText('First example feed item description.'); @@ -55,8 +55,8 @@ function testRSS091Sample() { 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->drupalGet('aggregator/sources/' . $feed->id()); + $this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label()))); $this->assertText('Atom-Powered Robots Run Amok'); $this->assertLinkByHref('http://example.org/2003/12/13/atom03'); $this->assertText('Some text.'); @@ -69,8 +69,8 @@ function testAtomSample() { function testHtmlEntitiesSample() { $feed = $this->createFeed($this->getHtmlEntitiesSample()); aggregator_refresh($feed); - $this->drupalGet('aggregator/sources/' . $feed->fid); - $this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->title))); + $this->drupalGet('aggregator/sources/' . $feed->id()); + $this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label()))); $this->assertRaw("Quote" Amp&"); } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedTest.php index b4ff56c..f16665d 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedTest.php @@ -29,11 +29,11 @@ function testRemoveFeed() { $this->deleteFeed($feed); // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->drupalGet('aggregator/sources/' . $feed->id()); $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(); + $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed->label(), ':url' => $feed->url->value))->fetchField(); $this->assertFalse($result, 'Feed not found in database'); } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedItemTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedItemTest.php index 42c1c15..54eaec5 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedItemTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedItemTest.php @@ -47,15 +47,16 @@ function testUpdateFeedItem() { $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(); + $fid = db_query("SELECT fid FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url']))->fetchField(); + $feed = aggregator_feed_load($fid); aggregator_refresh($feed); - $before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(); // Sleep for 3 second. sleep(3); db_update('aggregator_feed') - ->condition('fid', $feed->fid) + ->condition('fid', $feed->id()) ->fields(array( 'checked' => 0, 'hash' => '', @@ -65,7 +66,7 @@ function testUpdateFeedItem() { ->execute(); aggregator_refresh($feed); - $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField(); $this->assertTrue($before === $after, format_string('Publish timestamp of feed item was not updated (!before === !after)', array('!before' => $before, '!after' => $after))); } } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedTest.php index 751daa3..8b25bc7 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedTest.php @@ -30,18 +30,18 @@ function testUpdateFeed() { // 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}; + if (isset($feed->{$same_field}->value)) { + $edit[$same_field] = $feed->{$same_field}->value; } - $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->fid, $edit, t('Save')); + $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->id(), $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->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->drupalGet('aggregator/sources/' . $feed->id()); $this->assertResponse(200, 'Feed source exists.'); $this->assertText($edit['title'], 'Page title'); diff --git a/core/modules/aggregator/templates/aggregator-summary-items.tpl.php b/core/modules/aggregator/templates/aggregator-summary-items.tpl.php index 4a0551d..eec3683 100644 --- a/core/modules/aggregator/templates/aggregator-summary-items.tpl.php +++ b/core/modules/aggregator/templates/aggregator-summary-items.tpl.php @@ -8,8 +8,7 @@ * * Available variables: * - $title: Title of the feed or category. - * - $summary_list: Unordered list of linked feed items generated through - * theme_item_list(). + * - $summary_list: Render array of unordered feed items list. * - $source_url: URL to the local source or category. * * @see template_preprocess() @@ -19,7 +18,7 @@ */ ?>

- + diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 17015c6..a2af2a5 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -2281,6 +2281,12 @@ function system_data_type_info() { 'class' => '\Drupal\Core\Entity\Field\Type\EntityReferenceItem', 'list class' => '\Drupal\Core\Entity\Field\Type\Field', ), + 'uri_field' => array( + 'label' => t('URI field item'), + 'description' => t('An entity field containing a URI'), + 'class' => '\Drupal\Core\Entity\Field\Type\UriItem', + 'list class' => '\Drupal\Core\Entity\Field\Type\Field', + ), ); }