diff --git a/core/modules/aggregator/aggregator.views.inc b/core/modules/aggregator/aggregator.views.inc new file mode 100644 index 0000000..35e9611 --- /dev/null +++ b/core/modules/aggregator/aggregator.views.inc @@ -0,0 +1,337 @@ + 'iid', + 'title' => t('Aggregator item'), + 'help' => t('Aggregator items are imported from external RSS and Atom news feeds.'), + ); + $data['aggregator_item']['table']['entity type'] = 'aggregator_item'; + + $data['aggregator_item']['iid'] = array( + 'title' => t('Item ID'), + 'help' => t('The unique ID of the aggregator item.'), + 'field' => array( + 'id' => 'numeric', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'id' => 'aggregator_iid', + 'name field' => 'title', + 'numeric' => TRUE, + ), + 'filter' => array( + 'id' => 'numeric', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + + $data['aggregator_item']['title'] = array( + 'title' => t('Title'), + 'help' => t('The title of the aggregator item.'), + 'field' => array( + 'id' => 'aggregator_title_link', + 'extra' => array('link'), + 'click sortable' => TRUE, + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + 'filter' => array( + 'id' => 'string', + ), + ); + + $data['aggregator_item']['link'] = array( + 'title' => t('Link'), + 'help' => t('The link to the original source URL of the item.'), + 'field' => array( + 'id' => 'url', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + 'filter' => array( + 'id' => 'string', + ), + ); + + $data['aggregator_item']['author'] = array( + 'title' => t('Author'), + 'help' => t('The author of the original imported item.'), + 'field' => array( + 'id' => 'aggregator_xss', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + 'filter' => array( + 'id' => 'string', + ), + ); + + $data['aggregator_item']['guid'] = array( + 'title' => t('GUID'), + 'help' => t('The guid of the original imported item.'), + 'field' => array( + 'id' => 'xss', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + 'filter' => array( + 'id' => 'string', + ), + ); + + $data['aggregator_item']['description'] = array( + 'title' => t('Body'), + 'help' => t('The actual content of the imported item.'), + 'field' => array( + 'id' => 'aggregator_xss', + 'click sortable' => FALSE, + ), + 'argument' => array( + 'id' => 'string', + ), + 'filter' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + + $data['aggregator_item']['timestamp'] = array( + 'title' => t('Timestamp'), + 'help' => t('The date the original feed item was posted. (With some feeds, this will be the date it was imported.)'), + 'field' => array( + 'id' => 'date', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'id' => 'date', + ), + 'filter' => array( + 'id' => 'date', + ), + 'argument' => array( + 'id' => 'date', + ), + ); + + $data['aggregator_feed']['table']['group'] = t('Aggregator feed'); + + $data['aggregator_feed']['table']['base'] = array( + 'field' => 'fid', + 'title' => t('Aggregator feed'), + ); + + $data['aggregator_feed']['table']['entity type'] = 'aggregator_feed'; + + $data['aggregator_feed']['table']['join'] = array( + 'aggregator_item' => array( + 'left_field' => 'fid', + 'field' => 'fid', + ), + ); + + $data['aggregator_feed']['fid'] = array( + 'title' => t('Feed ID'), + 'help' => t('The unique ID of the aggregator feed.'), + 'field' => array( + 'id' => 'numeric', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'id' => 'aggregator_fid', + 'name field' => 'title', + 'numeric' => TRUE, + ), + 'filter' => array( + 'id' => 'numeric', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + + $data['aggregator_feed']['title'] = array( + 'title' => t('Title'), + 'help' => t('The title of the aggregator feed.'), + 'field' => array( + 'id' => 'aggregator_title_link', + 'extra' => array('link'), + 'click sortable' => TRUE, + ), + 'sort' => array( + 'id' => 'standard', + ), + 'filter' => array( + 'id' => 'string', + ), + 'argument' => array( + 'id' => 'string', + ), + ); + + $data['aggregator_feed']['link'] = array( + 'title' => t('Link'), + 'help' => t('The link to the source URL of the feed.'), + 'field' => array( + 'id' => 'url', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + 'filter' => array( + 'id' => 'string', + ), + ); + + $data['aggregator_feed']['checked'] = array( + 'title' => t('Last checked'), + 'help' => t('The date the feed was last checked for new content.'), + 'field' => array( + 'id' => 'date', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'id' => 'date', + ), + 'filter' => array( + 'id' => 'date', + ), + 'argument' => array( + 'id' => 'date', + ), + ); + + $data['aggregator_feed']['description'] = array( + 'title' => t('Description'), + 'help' => t('The description of the aggregator feed.'), + 'field' => array( + 'id' => 'xss', + 'click sortable' => FALSE, + ), + 'filter' => array( + 'id' => 'string', + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + + $data['aggregator_feed']['modified'] = array( + 'title' => t('Last modified'), + 'help' => t('The date of the most recent new content on the feed.'), + 'field' => array( + 'id' => 'date', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'id' => 'date', + ), + 'filter' => array( + 'id' => 'date', + ), + 'argument' => array( + 'id' => 'date', + ), + ); + + $data['aggregator_category_feed']['table']['join'] = array( + 'aggregator_item' => array( + 'left_field' => 'fid', + 'field' => 'fid', + ), + ); + + $data['aggregator_category']['table']['group'] = t('Aggregator category'); + + $data['aggregator_category']['table']['join'] = array( + 'aggregator_item' => array( + 'left_table' => 'aggregator_category_feed', + 'left_field' => 'cid', + 'field' => 'cid', + ), + ); + + $data['aggregator_category']['cid'] = array( + 'title' => t('Category ID'), + 'help' => t('The unique ID of the aggregator category.'), + 'field' => array( + 'id' => 'numeric', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'id' => 'aggregator_category_cid', + 'name field' => 'title', + 'numeric' => TRUE, + ), + 'filter' => array( + 'id' => 'aggregator_category_cid', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + + $data['aggregator_category']['title'] = array( + 'title' => t('Category'), + 'help' => t('The title of the aggregator category.'), + 'field' => array( + 'id' => 'aggregator_category', + 'click sortable' => TRUE, + ), + 'argument' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + 'filter' => array( + 'id' => 'string', + ), + ); + + return $data; +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageController.php b/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageController.php index 69876ab..993535a 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageController.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageController.php @@ -24,8 +24,9 @@ class ItemStorageController extends DatabaseStorageControllerNG { public function create(array $values) { $entity = parent::create($values); - // Set an initial timestamp, this will be overwritten if known. - $entity->timestamp->value = REQUEST_TIME; + if (!isset($entity->timestamp->value)) { + $entity->timestamp->value = REQUEST_TIME; + } return $entity; } diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/argument/CategoryCid.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/argument/CategoryCid.php new file mode 100644 index 0000000..7387dd2 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/argument/CategoryCid.php @@ -0,0 +1,36 @@ + $this->value))->fetchCol(); + + return array_map(function ($title) { + return check_plain($title); + }, $titles); + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/argument/Fid.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/argument/Fid.php new file mode 100644 index 0000000..f7b326a --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/argument/Fid.php @@ -0,0 +1,38 @@ +getStorageController('aggregator_feed')->load($this->value); + foreach ($feeds as $feed) { + $titles[] = check_plain($feed->label()); + } + return $titles; + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/argument/Iid.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/argument/Iid.php new file mode 100644 index 0000000..719369d --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/argument/Iid.php @@ -0,0 +1,38 @@ +getStorageController('aggregator_item')->load($this->value); + foreach ($items as $feed) { + $titles[] = check_plain($feed->label()); + } + return $titles; + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/field/Category.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/field/Category.php new file mode 100644 index 0000000..9972d03 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/field/Category.php @@ -0,0 +1,81 @@ +additional_fields['cid'] = 'cid'; + } + + /** + * Overrides \Drupal\views\Plugin\views\field\FieldPluginBase::defineOptions(). + */ + protected function defineOptions() { + $options = parent::defineOptions(); + + $options['link_to_category'] = array('default' => FALSE); + return $options; + } + + /** + * Overrides \Drupal\views\Plugin\views\field\FieldPluginBase::buildOptionsForm(). + */ + public function buildOptionsForm(&$form, &$form_state) { + $form['link_to_category'] = array( + '#title' => t('Link this field to its aggregator category page'), + '#description' => t('This will override any other link you have set.'), + '#type' => 'checkbox', + '#default_value' => !empty($this->options['link_to_category']), + ); + parent::buildOptionsForm($form, $form_state); + } + + /** + * Render whatever the data is as a link to the category. + * + * Data should be made XSS safe prior to calling this function. + */ + function render_link($data, $values) { + $cid = $this->get_value($values, 'cid'); + if (!empty($this->options['link_to_category']) && !empty($cid) && $data !== NULL && $data !== '') { + $this->options['alter']['make_link'] = TRUE; + $this->options['alter']['path'] = "aggregator/categories/$cid"; + } + return $data; + } + + /** + * Overrides \Drupal\views\Plugin\views\field\FieldPluginBase::render(). + */ + function render($values) { + $value = $this->get_value($values); + return $this->render_link($this->sanitizeValue($value), $values); + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/field/TitleLink.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/field/TitleLink.php new file mode 100644 index 0000000..8945de1 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/field/TitleLink.php @@ -0,0 +1,80 @@ +additional_fields['link'] = 'link'; + } + + /** + * Overrides \Drupal\views\Plugin\views\field\FieldPluginBase::defineOptions(). + */ + protected function defineOptions() { + $options = parent::defineOptions(); + + $options['display_as_link'] = array('default' => TRUE); + + return $options; + } + + /** + * Overrides \Drupal\views\Plugin\views\field\FieldPluginBase::buildOptionsForm(). + */ + public function buildOptionsForm(&$form, &$form_state) { + $form['display_as_link'] = array( + '#title' => t('Display as link'), + '#type' => 'checkbox', + '#default_value' => !empty($this->options['display_as_link']), + ); + parent::buildOptionsForm($form, $form_state); + } + + /** + * Overrides \Drupal\views\Plugin\views\field\FieldPluginBase::render(). + */ + function render($values) { + $value = $this->get_value($values); + return $this->render_link($this->sanitizeValue($value), $values); + } + + protected function render_link($data, $values) { + $link = $this->get_value($values, 'link'); + if (!empty($this->options['display_as_link'])) { + $this->options['alter']['make_link'] = TRUE; + $this->options['alter']['path'] = $link; + $this->options['alter']['html'] = TRUE; + $this->options['alter']['absolute'] = TRUE; + } + + return $data; + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/field/Xss.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/field/Xss.php new file mode 100644 index 0000000..609ed5a --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/field/Xss.php @@ -0,0 +1,37 @@ +value_options)) { + return; + } + + $this->value_options = array(); + $this->value_options = db_query('SELECT cid, title FROM {aggregator_category} ORDER BY title')->fetchAllKeyed(); + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/row/Rss.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/row/Rss.php new file mode 100644 index 0000000..89d2c3d --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/views/row/Rss.php @@ -0,0 +1,105 @@ + 'default'); + + return $options; + } + + /** + * Overrides \Drupal\views\Plugin\views\row\RowPluginBase::buildOptionsForm(). + */ + public function buildOptionsForm(&$form, &$form_state) { + $form['item_length'] = array( + '#type' => 'select', + '#title' => t('Display type'), + '#options' => array( + 'fulltext' => t('Full text'), + 'teaser' => t('Title plus teaser'), + 'title' => t('Title only'), + 'default' => t('Use default RSS settings'), + ), + '#default_value' => $this->options['item_length'], + ); + } + + /** + * Overrides \Drupal\views\Plugin\views\row\RowPluginBase::render(). + */ + function render($row) { + $entity = $row->_entity; + + $item = new \stdClass(); + foreach ($entity->getProperties() as $name => $value) { + $item->{$name} = $value->value; + } + + $item->elements = array( + array( + 'key' => 'pubDate', + 'value' => gmdate('r', $entity->timestamp->value), + ), + array( + 'key' => 'dc:creator', + 'value' => $entity->author->value, + ), + array( + 'key' => 'guid', + 'value' => $entity->guid->value, + 'attributes' => array('isPermaLink' => 'false'), + ), + ); + + return theme($this->themeFunctions(), array( + 'view' => $this->view, + 'options' => $this->options, + 'row' => $item, + )); + } + +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/Views/IntegrationTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/Views/IntegrationTest.php new file mode 100644 index 0000000..ad63b96 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/Views/IntegrationTest.php @@ -0,0 +1,117 @@ + 'Aggregator: Integration tests', + 'description' => 'Tests basic integration of views data from the aggregator module.', + 'group' => 'Views module integration', + ); + } + + protected function setUp() { + parent::setUp(); + + $this->installSchema('aggregator', array('aggregator_item', 'aggregator_feed', 'aggregator_category_feed', 'aggregator_category', 'aggregator_category_item')); + $this->installSchema('field', array('field_config', 'field_config_instance')); + + ViewTestData::importTestViews(get_class($this), array('aggregator_test_views')); + + $this->itemStorageController = $this->container->get('plugin.manager.entity')->getStorageController('aggregator_item'); + $this->feedStorageController = $this->container->get('plugin.manager.entity')->getStorageController('aggregator_feed'); + } + + /** + * Tests basic aggregator_item view. + */ + public function testAggregatorItemView() { + $items = array(); + $expected = array(); + for ($i = 0; $i < 10; $i++) { + $values = array(); + $values['timestamp'] = mt_rand(REQUEST_TIME - 10, REQUEST_TIME + 10); + $values['title'] = $this->randomName(); + $values['description'] = $this->randomName(); + // Add a image to ensure that the sanitizing can be tested below. + $values['author'] = $this->randomName() . '"'; + $values['link'] = 'http://drupal.org/node/' . mt_rand(1000, 10000); + + $aggregator_item = $this->itemStorageController->create($values); + $aggregator_item->save(); + $items[$aggregator_item->id()] = $aggregator_item; + + $values['iid'] = $aggregator_item->id(); + $expected[] = $values; + } + + $view = views_get_view('test_aggregator_items'); + $this->executeView($view); + + $column_map = array( + 'iid' => 'iid', + 'aggregator_item_title' => 'title', + 'aggregator_item_timestamp' => 'timestamp', + 'aggregator_item_description' => 'description', + 'aggregator_item_author' => 'author', + ); + $this->assertIdenticalResultset($view, $expected, $column_map); + + // Ensure that the rendering of the linked title works as expected. + foreach ($view->result as $row) { + $iid = $view->field['iid']->get_value($row); + $expected_link = l($items[$iid]->title->value, $items[$iid]->link->value, array('absolute' => TRUE)); + + $this->assertEqual($view->field['title']->advanced_render($row), $expected_link, 'Ensure the right link is generated'); + } + + // Ensure that the rendering of the description of the author works. + foreach ($view->result as $row) { + $iid = $view->field['iid']->get_value($row); + $expected_author = filter_xss($items[$iid]->author->value, array()); + $this->assertEqual($view->field['author']->advanced_render($row), $expected_author, 'Ensure the author got filtered'); + } + } + +} diff --git a/core/modules/aggregator/tests/modules/aggregator_test_views/aggregator_test_views.info.yml b/core/modules/aggregator/tests/modules/aggregator_test_views/aggregator_test_views.info.yml new file mode 100644 index 0000000..2165bd7 --- /dev/null +++ b/core/modules/aggregator/tests/modules/aggregator_test_views/aggregator_test_views.info.yml @@ -0,0 +1,9 @@ +name: 'Aggregator test views' +description: 'Provides default views for views aggregator tests.' +package: Testing +version: VERSION +core: 8.x +dependencies: + - aggregator + - views +hidden: true diff --git a/core/modules/aggregator/tests/modules/aggregator_test_views/aggregator_test_views.module b/core/modules/aggregator/tests/modules/aggregator_test_views/aggregator_test_views.module new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/core/modules/aggregator/tests/modules/aggregator_test_views/aggregator_test_views.module @@ -0,0 +1 @@ +