Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
web/core/modules/aggregator
aggregator.info.ymlaggregator.installaggregator.links.action.ymlaggregator.links.menu.ymlaggregator.links.task.ymlaggregator.moduleaggregator.permissions.ymlaggregator.routing.ymlaggregator.services.ymlaggregator.theme.inc
config
install
aggregator.settings.ymlcore.entity_view_display.aggregator_feed.aggregator_feed.default.ymlcore.entity_view_display.aggregator_feed.aggregator_feed.summary.ymlcore.entity_view_display.aggregator_item.aggregator_item.summary.ymlcore.entity_view_mode.aggregator_feed.summary.ymlcore.entity_view_mode.aggregator_item.summary.yml
optional
schema
migration_templates
d6_aggregator_feed.ymld6_aggregator_item.ymld6_aggregator_settings.ymld7_aggregator_feed.ymld7_aggregator_item.ymld7_aggregator_settings.yml
src
AggregatorFeedViewsData.phpAggregatorItemViewsData.php
Annotation
Controller
Entity
FeedAccessControlHandler.phpFeedForm.phpFeedHtmlRouteProvider.phpFeedInterface.phpFeedStorage.phpFeedStorageInterface.phpFeedStorageSchema.phpFeedViewBuilder.phpForm
ItemInterface.phpItemStorage.phpItemStorageInterface.phpItemStorageSchema.phpItemViewBuilder.phpItemsImporter.phpItemsImporterInterface.phpPlugin
AggregatorPluginManager.phpAggregatorPluginSettingsBase.php
Block
FetcherInterface.phpField/FieldFormatter
ParserInterface.phpProcessorInterface.phpQueueWorker
Validation/Constraint
aggregator
migrate/source
views
Tests
AddFeedTest.phpAggregatorAdminTest.phpAggregatorCronTest.phpAggregatorRenderingTest.phpAggregatorTestBase.phpDeleteFeedItemTest.phpDeleteFeedTest.phpFeedAdminDisplayTest.phpFeedCacheTagsTest.phpFeedFetcherPluginTest.phpFeedLanguageTest.phpFeedParserTest.phpFeedProcessorPluginTest.phpImportOpmlTest.phpItemCacheTagsTest.php
Update
UpdateFeedItemTest.phpUpdateFeedTest.phptemplates
tests/modules/aggregator_test
10
web/core/modules/aggregator/aggregator.info.yml
Normal file
10
web/core/modules/aggregator/aggregator.info.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: Aggregator
|
||||
type: module
|
||||
description: 'Aggregates syndicated content (RSS, RDF, and Atom feeds) from external sources.'
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
configure: aggregator.admin_settings
|
||||
dependencies:
|
||||
- file
|
||||
- options
|
60
web/core/modules/aggregator/aggregator.install
Normal file
60
web/core/modules/aggregator/aggregator.install
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the aggregator module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function aggregator_requirements($phase) {
|
||||
$has_curl = function_exists('curl_init');
|
||||
$requirements = array();
|
||||
$requirements['curl'] = array(
|
||||
'title' => t('cURL'),
|
||||
'value' => $has_curl ? t('Enabled') : t('Not found'),
|
||||
);
|
||||
if (!$has_curl) {
|
||||
$requirements['curl']['severity'] = REQUIREMENT_ERROR;
|
||||
$requirements['curl']['description'] = t('The Aggregator module could not be installed because the PHP <a href="http://php.net/manual/curl.setup.php">cURL</a> library is not available.');
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.0.0-rc
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* The simple presence of this update function clears cached field definitions.
|
||||
*/
|
||||
function aggregator_update_8001() {
|
||||
// Feed ID base field is now required.
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.0.0-rc".
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.2.x
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Make the 'Source feed' field for aggregator items required.
|
||||
*/
|
||||
function aggregator_update_8200() {
|
||||
// aggregator_update_8001() did not update the last installed field storage
|
||||
// definition for the aggregator item's 'Source feed' field.
|
||||
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$field_definition = $definition_update_manager->getFieldStorageDefinition('fid', 'aggregator_item');
|
||||
$field_definition->setRequired(TRUE);
|
||||
$definition_update_manager->updateFieldStorageDefinition($field_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.2.x".
|
||||
*/
|
11
web/core/modules/aggregator/aggregator.links.action.yml
Normal file
11
web/core/modules/aggregator/aggregator.links.action.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
aggregator.feed_add:
|
||||
route_name: aggregator.feed_add
|
||||
title: 'Add feed'
|
||||
appears_on:
|
||||
- 'aggregator.admin_overview'
|
||||
|
||||
aggregator.opml_add:
|
||||
route_name: aggregator.opml_add
|
||||
title: 'Import OPML'
|
||||
appears_on:
|
||||
- 'aggregator.admin_overview'
|
13
web/core/modules/aggregator/aggregator.links.menu.yml
Normal file
13
web/core/modules/aggregator/aggregator.links.menu.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
aggregator.admin_overview:
|
||||
title: 'Aggregator'
|
||||
description: 'Add feeds or import OPMLs to gather external content and configure how often they are updated.'
|
||||
route_name: aggregator.admin_overview
|
||||
parent: system.admin_config_services
|
||||
weight: 10
|
||||
aggregator.page_last:
|
||||
title: 'Aggregator'
|
||||
weight: 5
|
||||
route_name: aggregator.page_last
|
||||
aggregator.feed_add:
|
||||
title: 'Add feed'
|
||||
route_name: aggregator.feed_add
|
24
web/core/modules/aggregator/aggregator.links.task.yml
Normal file
24
web/core/modules/aggregator/aggregator.links.task.yml
Normal file
|
@ -0,0 +1,24 @@
|
|||
aggregator.admin_overview:
|
||||
route_name: aggregator.admin_overview
|
||||
title: 'List'
|
||||
base_route: aggregator.admin_overview
|
||||
aggregator.admin_settings:
|
||||
route_name: aggregator.admin_settings
|
||||
title: 'Settings'
|
||||
weight: 100
|
||||
base_route: aggregator.admin_overview
|
||||
|
||||
entity.aggregator_feed.canonical:
|
||||
route_name: entity.aggregator_feed.canonical
|
||||
base_route: entity.aggregator_feed.canonical
|
||||
title: View
|
||||
entity.aggregator_feed.edit_form:
|
||||
route_name: entity.aggregator_feed.edit_form
|
||||
base_route: entity.aggregator_feed.canonical
|
||||
title: 'Configure'
|
||||
weight: 10
|
||||
entity.aggregator_feed.delete_form:
|
||||
route_name: entity.aggregator_feed.delete_form
|
||||
base_route: entity.aggregator_feed.canonical
|
||||
title: Delete
|
||||
weight: 20
|
182
web/core/modules/aggregator/aggregator.module
Normal file
182
web/core/modules/aggregator/aggregator.module
Normal file
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Used to aggregate syndicated content (RSS, RDF, and Atom).
|
||||
*/
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Denotes that a feed's items should never expire.
|
||||
*/
|
||||
const AGGREGATOR_CLEAR_NEVER = 0;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function aggregator_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.aggregator':
|
||||
$path_validator = \Drupal::pathValidator();
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Aggregator module is an on-site syndicator and news reader that gathers and displays fresh content from RSS-, RDF-, and Atom-based feeds made available across the web. Thousands of sites (particularly news sites and blogs) publish their latest headlines in feeds, using a number of standardized XML-based formats. For more information, see the <a href=":aggregator-module">online documentation for the Aggregator module</a>.', array(':aggregator-module' => 'https://www.drupal.org/documentation/modules/aggregator')) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
// Check if the aggregator sources View is enabled.
|
||||
if ($url = $path_validator->getUrlIfValid('aggregator/sources')) {
|
||||
$output .= '<dt>' . t('Viewing feeds') . '</dt>';
|
||||
$output .= '<dd>' . t('Users view feed content in the <a href=":aggregator">main aggregator display</a>, or by <a href=":aggregator-sources">their source</a> (usually via an RSS feed reader). The most recent content in a feed can be displayed as a block through the <a href=":admin-block">Blocks administration page</a>.', array(':aggregator' => \Drupal::url('aggregator.page_last'), ':aggregator-sources' => $url->toString(), ':admin-block' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#')) . '</dd>';
|
||||
}
|
||||
$output .= '<dt>' . t('Adding, editing, and deleting feeds') . '</dt>';
|
||||
$output .= '<dd>' . t('Administrators can add, edit, and delete feeds, and choose how often to check each feed for newly updated items on the <a href=":feededit">Aggregator administration page</a>.', array(':feededit' => \Drupal::url('aggregator.admin_overview'))) . '</dd>';
|
||||
$output .= '<dt>' . t('Configuring the display of feed items') . '</dt>';
|
||||
$output .= '<dd>' . t('Administrators can choose how many items are displayed in the listing pages, which HTML tags are allowed in the content of feed items, and whether they should be trimmed to a maximum number of characters on the <a href=":settings">Aggregator settings page</a>.', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '</dd>';
|
||||
$output .= '<dt>' . t('Discarding old feed items') . '</dt>';
|
||||
$output .= '<dd>' . t('Administrators can choose whether to discard feed items that are older than a specified period of time on the <a href=":settings">Aggregator settings page</a>. This requires a correctly configured cron maintenance task (see below).', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '<dd>';
|
||||
|
||||
$output .= '<dt>' . t('<abbr title="Outline Processor Markup Language">OPML</abbr> integration') . '</dt>';
|
||||
// Check if the aggregator opml View is enabled.
|
||||
if ($url = $path_validator->getUrlIfValid('aggregator/opml')) {
|
||||
$output .= '<dd>' . t('A <a href=":aggregator-opml">machine-readable OPML file</a> of all feeds is available. OPML is an XML-based file format used to share outline-structured information such as a list of RSS feeds. Feeds can also be <a href=":import-opml">imported via an OPML file</a>.', array(':aggregator-opml' => $url->toString(), ':import-opml' => \Drupal::url('aggregator.opml_add'))) . '</dd>';
|
||||
}
|
||||
$output .= '<dt>' . t('Configuring cron') . '</dt>';
|
||||
$output .= '<dd>' . t('A working <a href=":cron">cron maintenance task</a> is required to update feeds automatically.', array(':cron' => \Drupal::url('system.cron_settings'))) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
|
||||
case 'aggregator.admin_overview':
|
||||
// Don't use placeholders for possibility to change URLs for translators.
|
||||
$output = '<p>' . t('Many sites publish their headlines and posts in feeds, using a number of standardized XML-based formats. The aggregator supports <a href="http://en.wikipedia.org/wiki/Rss">RSS</a>, <a href="http://en.wikipedia.org/wiki/Resource_Description_Framework">RDF</a>, and <a href="http://en.wikipedia.org/wiki/Atom_%28standard%29">Atom</a>.') . '</p>';
|
||||
$output .= '<p>' . t('Current feeds are listed below, and <a href=":addfeed">new feeds may be added</a>. For each feed, the <em>latest items</em> block may be enabled at the <a href=":block">blocks administration page</a>.', array(':addfeed' => \Drupal::url('aggregator.feed_add'), ':block' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#')) . '</p>';
|
||||
return $output;
|
||||
|
||||
case 'aggregator.feed_add':
|
||||
return '<p>' . t('Add a feed in RSS, RDF or Atom format. A feed may only have one entry.') . '</p>';
|
||||
|
||||
case 'aggregator.opml_add':
|
||||
return '<p>' . t('<abbr title="Outline Processor Markup Language">OPML</abbr> is an XML format for exchanging feeds between aggregators. A single OPML document may contain many feeds. Aggregator uses this file to import all feeds at once. Upload a file from your computer or enter a URL where the OPML file can be downloaded.') . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function aggregator_theme() {
|
||||
return array(
|
||||
'aggregator_feed' => array(
|
||||
'render element' => 'elements',
|
||||
'file' => 'aggregator.theme.inc',
|
||||
),
|
||||
'aggregator_item' => array(
|
||||
'render element' => 'elements',
|
||||
'file' => 'aggregator.theme.inc',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_extra_field_info().
|
||||
*/
|
||||
function aggregator_entity_extra_field_info() {
|
||||
$extra = array();
|
||||
|
||||
$extra['aggregator_feed']['aggregator_feed'] = array(
|
||||
'display' => array(
|
||||
'items' => array(
|
||||
'label' => t('Items'),
|
||||
'description' => t('Items associated with this feed'),
|
||||
'weight' => 0,
|
||||
),
|
||||
// @todo Move to a formatter at https://www.drupal.org/node/2339917.
|
||||
'image' => array(
|
||||
'label' => t('Image'),
|
||||
'description' => t('The feed image'),
|
||||
'weight' => 2,
|
||||
),
|
||||
// @todo Move to a formatter at https://www.drupal.org/node/2149845.
|
||||
'description' => array(
|
||||
'label' => t('Description'),
|
||||
'description' => t('The description of this feed'),
|
||||
'weight' => 3,
|
||||
),
|
||||
'more_link' => array(
|
||||
'label' => t('More link'),
|
||||
'description' => t('A more link to the feed detail page'),
|
||||
'weight' => 5,
|
||||
),
|
||||
'feed_icon' => array(
|
||||
'label' => t('Feed icon'),
|
||||
'description' => t('An icon that links to the feed URL'),
|
||||
'weight' => 6,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$extra['aggregator_item']['aggregator_item'] = array(
|
||||
'display' => array(
|
||||
// @todo Move to a formatter at https://www.drupal.org/node/2149845.
|
||||
'description' => array(
|
||||
'label' => t('Description'),
|
||||
'description' => t('The description of this feed item'),
|
||||
'weight' => 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_cron().
|
||||
*
|
||||
* Queues news feeds for updates once their refresh interval has elapsed.
|
||||
*/
|
||||
function aggregator_cron() {
|
||||
$queue = \Drupal::queue('aggregator_feeds');
|
||||
|
||||
$ids = \Drupal::entityManager()->getStorage('aggregator_feed')->getFeedIdsToRefresh();
|
||||
foreach (Feed::loadMultiple($ids) as $feed) {
|
||||
if ($queue->createItem($feed)) {
|
||||
// Add timestamp to avoid queueing item more than once.
|
||||
$feed->setQueuedTime(REQUEST_TIME);
|
||||
$feed->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete queued timestamp after 6 hours assuming the update has failed.
|
||||
$ids = \Drupal::entityQuery('aggregator_feed')
|
||||
->condition('queued', REQUEST_TIME - (3600 * 6), '<')
|
||||
->execute();
|
||||
|
||||
if ($ids) {
|
||||
$feeds = Feed::loadMultiple($ids);
|
||||
foreach ($feeds as $feed) {
|
||||
$feed->setQueuedTime(0);
|
||||
$feed->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of allowed tags.
|
||||
*
|
||||
* @return array
|
||||
* The list of allowed tags.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function _aggregator_allowed_tags() {
|
||||
return preg_split('/\s+|<|>/', \Drupal::config('aggregator.settings')->get('items.allowed_html'), -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK() for block templates.
|
||||
*/
|
||||
function aggregator_preprocess_block(&$variables) {
|
||||
if ($variables['configuration']['provider'] == 'aggregator') {
|
||||
$variables['attributes']['role'] = 'complementary';
|
||||
}
|
||||
}
|
4
web/core/modules/aggregator/aggregator.permissions.yml
Normal file
4
web/core/modules/aggregator/aggregator.permissions.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
administer news feeds:
|
||||
title: 'Administer news feeds'
|
||||
access news feeds:
|
||||
title: 'View news feeds'
|
58
web/core/modules/aggregator/aggregator.routing.yml
Normal file
58
web/core/modules/aggregator/aggregator.routing.yml
Normal file
|
@ -0,0 +1,58 @@
|
|||
aggregator.admin_overview:
|
||||
path: '/admin/config/services/aggregator'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator\Controller\AggregatorController::adminOverview'
|
||||
_title: 'Aggregator'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
|
||||
aggregator.admin_settings:
|
||||
path: '/admin/config/services/aggregator/settings'
|
||||
defaults:
|
||||
_form: '\Drupal\aggregator\Form\SettingsForm'
|
||||
_title: 'Aggregator settings'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
|
||||
aggregator.feed_items_delete:
|
||||
path: '/admin/config/services/aggregator/delete/{aggregator_feed}'
|
||||
defaults:
|
||||
_entity_form: 'aggregator_feed.delete_items'
|
||||
_title: 'Delete items'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
|
||||
aggregator.feed_refresh:
|
||||
path: '/admin/config/services/aggregator/update/{aggregator_feed}'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator\Controller\AggregatorController::feedRefresh'
|
||||
_title: 'Update items'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
_csrf_token: 'TRUE'
|
||||
|
||||
aggregator.opml_add:
|
||||
path: '/admin/config/services/aggregator/add/opml'
|
||||
defaults:
|
||||
_form: '\Drupal\aggregator\Form\OpmlFeedAdd'
|
||||
_title: 'Import OPML'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
|
||||
aggregator.feed_add:
|
||||
path: '/aggregator/sources/add'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator\Controller\AggregatorController::feedAdd'
|
||||
_title: 'Add feed'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
|
||||
aggregator.page_last:
|
||||
path: '/aggregator'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator\Controller\AggregatorController::pageLast'
|
||||
_title: 'Aggregator'
|
||||
requirements:
|
||||
_permission: 'access news feeds'
|
16
web/core/modules/aggregator/aggregator.services.yml
Normal file
16
web/core/modules/aggregator/aggregator.services.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
services:
|
||||
plugin.manager.aggregator.fetcher:
|
||||
class: Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
arguments: [fetcher, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
plugin.manager.aggregator.parser:
|
||||
class: Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
arguments: [parser, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
plugin.manager.aggregator.processor:
|
||||
class: Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
arguments: [processor, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
aggregator.items.importer:
|
||||
class: Drupal\aggregator\ItemsImporter
|
||||
arguments: ['@config.factory', '@plugin.manager.aggregator.fetcher', '@plugin.manager.aggregator.parser', '@plugin.manager.aggregator.processor', '@logger.channel.aggregator']
|
||||
logger.channel.aggregator:
|
||||
parent: logger.channel_base
|
||||
arguments: ['aggregator']
|
50
web/core/modules/aggregator/aggregator.theme.inc
Normal file
50
web/core/modules/aggregator/aggregator.theme.inc
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Preprocessors and theme functions of Aggregator module.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
/**
|
||||
* Prepares variables for aggregator item templates.
|
||||
*
|
||||
* Default template: aggregator-item.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - elements: An array of elements to display in view mode.
|
||||
*/
|
||||
function template_preprocess_aggregator_item(&$variables) {
|
||||
$item = $variables['elements']['#aggregator_item'];
|
||||
|
||||
// Helpful $content variable for templates.
|
||||
foreach (Element::children($variables['elements']) as $key) {
|
||||
$variables['content'][$key] = $variables['elements'][$key];
|
||||
}
|
||||
|
||||
$variables['url'] = UrlHelper::stripDangerousProtocols($item->getLink());
|
||||
$variables['title'] = $item->label();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for aggregator feed templates.
|
||||
*
|
||||
* Default template: aggregator-feed.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - elements: An array of elements to display in view mode.
|
||||
*/
|
||||
function template_preprocess_aggregator_feed(&$variables) {
|
||||
$feed = $variables['elements']['#aggregator_feed'];
|
||||
|
||||
// Helpful $content variable for templates.
|
||||
foreach (Element::children($variables['elements']) as $key) {
|
||||
$variables['content'][$key] = $variables['elements'][$key];
|
||||
}
|
||||
$variables['full'] = $variables['elements']['#view_mode'] == 'full';
|
||||
$variables['title'] = $feed->label();
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
fetcher: aggregator
|
||||
parser: aggregator
|
||||
processors:
|
||||
- aggregator
|
||||
items:
|
||||
allowed_html: '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'
|
||||
teaser_length: 600
|
||||
expire: 9676800
|
||||
source:
|
||||
list_max: 3
|
|
@ -0,0 +1,32 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_feed.aggregator_feed.default
|
||||
targetEntityType: aggregator_feed
|
||||
bundle: aggregator_feed
|
||||
mode: default
|
||||
content:
|
||||
checked:
|
||||
type: timestamp_ago
|
||||
weight: 1
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
label: inline
|
||||
description:
|
||||
weight: 3
|
||||
feed_icon:
|
||||
weight: 5
|
||||
image:
|
||||
weight: 2
|
||||
items:
|
||||
weight: 0
|
||||
link:
|
||||
type: uri_link
|
||||
weight: 4
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
label: inline
|
||||
hidden:
|
||||
more_link: true
|
|
@ -0,0 +1,22 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.aggregator_feed.summary
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_feed.aggregator_feed.summary
|
||||
targetEntityType: aggregator_feed
|
||||
bundle: aggregator_feed
|
||||
mode: summary
|
||||
content:
|
||||
items:
|
||||
weight: 0
|
||||
more_link:
|
||||
weight: 1
|
||||
hidden:
|
||||
checked: true
|
||||
description: true
|
||||
feed_icon: true
|
||||
image: true
|
||||
link: true
|
|
@ -0,0 +1,19 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.aggregator_item.summary
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_item.aggregator_item.summary
|
||||
targetEntityType: aggregator_item
|
||||
bundle: aggregator_item
|
||||
mode: summary
|
||||
content:
|
||||
timestamp:
|
||||
weight: 0
|
||||
hidden:
|
||||
author: true
|
||||
description: true
|
||||
feed: true
|
||||
link: true
|
|
@ -0,0 +1,9 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_feed.summary
|
||||
label: Summary
|
||||
targetEntityType: aggregator_feed
|
||||
cache: true
|
|
@ -0,0 +1,9 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_item.summary
|
||||
label: Summary
|
||||
targetEntityType: aggregator_item
|
||||
cache: true
|
|
@ -0,0 +1,156 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_rss_feed
|
||||
label: 'Aggregator RSS feed'
|
||||
module: aggregator
|
||||
description: ''
|
||||
tag: aggregator
|
||||
base_table: aggregator_item
|
||||
base_field: iid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access news feeds'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: 0
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
quantity: 9
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: 'entity:aggregator_item'
|
||||
fields:
|
||||
iid:
|
||||
table: aggregator_item
|
||||
field: iid
|
||||
id: iid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Item ID'
|
||||
exclude: false
|
||||
plugin_id: field
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: number_integer
|
||||
entity_type: aggregator_item
|
||||
entity_field: iid
|
||||
filters: { }
|
||||
sorts: { }
|
||||
title: 'Aggregator RSS feed'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
cacheable: false
|
||||
feed_items:
|
||||
display_plugin: feed
|
||||
id: feed_items
|
||||
display_title: Feed
|
||||
position: 1
|
||||
display_options:
|
||||
path: aggregator/rss
|
||||
display_description: ''
|
||||
defaults:
|
||||
arguments: true
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user.permissions
|
||||
cacheable: false
|
|
@ -0,0 +1,426 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.aggregator_feed.summary
|
||||
- system.menu.tools
|
||||
module:
|
||||
- aggregator
|
||||
- user
|
||||
id: aggregator_sources
|
||||
label: 'Aggregator sources'
|
||||
module: aggregator
|
||||
description: ''
|
||||
tag: aggregator
|
||||
base_table: aggregator_feed
|
||||
base_field: fid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access news feeds'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
quantity: 9
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: 'entity:aggregator_feed'
|
||||
options:
|
||||
relationship: none
|
||||
view_mode: summary
|
||||
fields:
|
||||
fid:
|
||||
table: aggregator_feed
|
||||
field: fid
|
||||
id: fid
|
||||
plugin_id: field
|
||||
type: number_integer
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
entity_type: aggregator_feed
|
||||
entity_field: fid
|
||||
filters: { }
|
||||
sorts: { }
|
||||
title: Sources
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
max-age: 0
|
||||
feed_1:
|
||||
display_plugin: feed
|
||||
id: feed_1
|
||||
display_title: Feed
|
||||
position: 2
|
||||
display_options:
|
||||
style:
|
||||
type: opml
|
||||
options:
|
||||
grouping: { }
|
||||
path: aggregator/opml
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: aggregator_feed
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: aggregator_title
|
||||
settings:
|
||||
display_as_link: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
plugin_id: field
|
||||
entity_type: aggregator_feed
|
||||
entity_field: title
|
||||
url:
|
||||
id: url
|
||||
table: aggregator_feed
|
||||
field: url
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
display_as_link: false
|
||||
plugin_id: url
|
||||
entity_type: aggregator_feed
|
||||
entity_field: url
|
||||
description:
|
||||
id: description
|
||||
table: aggregator_feed
|
||||
field: description
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: xss
|
||||
entity_type: aggregator_feed
|
||||
entity_field: description
|
||||
link:
|
||||
id: link
|
||||
table: aggregator_feed
|
||||
field: link
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
display_as_link: false
|
||||
plugin_id: url
|
||||
entity_type: aggregator_feed
|
||||
entity_field: link
|
||||
defaults:
|
||||
fields: false
|
||||
title: false
|
||||
row:
|
||||
type: opml_fields
|
||||
options:
|
||||
type_field: rss
|
||||
text_field: title
|
||||
created_field: ''
|
||||
description_field: description
|
||||
html_url_field: link
|
||||
language_field: ''
|
||||
xml_url_field: url
|
||||
url_field: ''
|
||||
displays:
|
||||
page_1: page_1
|
||||
default: '0'
|
||||
title: 'OPML feed'
|
||||
sitename_title: true
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user.permissions
|
||||
max-age: 0
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
path: aggregator/sources
|
||||
menu:
|
||||
type: normal
|
||||
title: Sources
|
||||
description: ''
|
||||
weight: 0
|
||||
context: '0'
|
||||
menu_name: tools
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
max-age: 0
|
|
@ -0,0 +1,57 @@
|
|||
# Schema for the configuration files of the aggregator module.
|
||||
|
||||
aggregator.settings:
|
||||
type: config_object
|
||||
label: 'Aggregator settings'
|
||||
mapping:
|
||||
fetcher:
|
||||
type: string
|
||||
label: 'Fetcher'
|
||||
parser:
|
||||
type: string
|
||||
label: 'Parser'
|
||||
processors:
|
||||
type: sequence
|
||||
label: 'Processors'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Processor'
|
||||
items:
|
||||
type: mapping
|
||||
label: 'Items'
|
||||
mapping:
|
||||
allowed_html:
|
||||
type: string
|
||||
label: 'Allowed HTML tags'
|
||||
teaser_length:
|
||||
type: integer
|
||||
label: 'Length of trimmed description'
|
||||
expire:
|
||||
type: integer
|
||||
label: 'Discard items older than'
|
||||
source:
|
||||
type: mapping
|
||||
label: 'Source'
|
||||
mapping:
|
||||
list_max:
|
||||
type: integer
|
||||
label: 'Number of items shown in listing pages'
|
||||
|
||||
block.settings.aggregator_feed_block:
|
||||
type: block_settings
|
||||
label: 'Aggregator feed block'
|
||||
mapping:
|
||||
block_count:
|
||||
type: integer
|
||||
label: 'Block count'
|
||||
feed:
|
||||
type: string
|
||||
label: 'Feed'
|
||||
|
||||
field.formatter.settings.aggregator_title:
|
||||
type: mapping
|
||||
label: 'Formatter settings'
|
||||
mapping:
|
||||
display_as_link:
|
||||
type: boolean
|
||||
label: 'Display as link'
|
|
@ -0,0 +1,24 @@
|
|||
# Schema for the views plugins of the Aggregator module.
|
||||
|
||||
views.argument.aggregator_fid:
|
||||
type: views.argument.numeric
|
||||
label: 'Aggregator feed ID'
|
||||
|
||||
views.argument.aggregator_iid:
|
||||
type: views.argument.numeric
|
||||
label: 'Aggregator item ID'
|
||||
|
||||
views.field.aggregator_title_link:
|
||||
type: views_field
|
||||
label: 'Title link'
|
||||
mapping:
|
||||
display_as_link:
|
||||
type: boolean
|
||||
|
||||
views.row.aggregator_rss:
|
||||
type: views_row
|
||||
label: 'Aggregator item row'
|
||||
mapping:
|
||||
view_mode:
|
||||
type: string
|
||||
label: 'Display type'
|
|
@ -0,0 +1,19 @@
|
|||
id: d6_aggregator_feed
|
||||
label: Aggregator feeds
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
source:
|
||||
plugin: aggregator_feed
|
||||
process:
|
||||
fid: fid
|
||||
title: title
|
||||
url: url
|
||||
refresh: refresh
|
||||
checked: checked
|
||||
link: link
|
||||
description: description
|
||||
image: image
|
||||
etag: etag
|
||||
modified: modified
|
||||
destination:
|
||||
plugin: entity:aggregator_feed
|
|
@ -0,0 +1,23 @@
|
|||
id: d6_aggregator_item
|
||||
label: Aggregator items
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
source:
|
||||
plugin: aggregator_item
|
||||
process:
|
||||
iid: iid
|
||||
fid:
|
||||
plugin: migration
|
||||
migration: d6_aggregator_feed
|
||||
source: fid
|
||||
title: title
|
||||
link: link
|
||||
author: author
|
||||
description: description
|
||||
timestamp: timestamp
|
||||
guid: guid
|
||||
destination:
|
||||
plugin: entity:aggregator_item
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_aggregator_feed
|
|
@ -0,0 +1,25 @@
|
|||
id: d6_aggregator_settings
|
||||
label: Aggregator configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- aggregator_fetcher
|
||||
- aggregator_parser
|
||||
- aggregator_processors
|
||||
- aggregator_allowed_html_tags
|
||||
- aggregator_teaser_length
|
||||
- aggregator_clear
|
||||
- aggregator_summary_items
|
||||
process:
|
||||
fetcher: aggregator_fetcher
|
||||
parser: aggregator_parser
|
||||
processors: aggregator_processors
|
||||
'items/allowed_html': aggregator_allowed_html_tags
|
||||
'items/teaser_length': aggregator_teaser_length
|
||||
'items/expire': aggregator_clear
|
||||
'source/list_max': aggregator_summary_items
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: aggregator.settings
|
|
@ -0,0 +1,20 @@
|
|||
id: d7_aggregator_feed
|
||||
label: Aggregator feeds
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
source:
|
||||
plugin: aggregator_feed
|
||||
process:
|
||||
fid: fid
|
||||
title: title
|
||||
url: url
|
||||
refresh: refresh
|
||||
checked: checked
|
||||
queued: queued
|
||||
link: link
|
||||
description: description
|
||||
image: image
|
||||
etag: etag
|
||||
modified: modified
|
||||
destination:
|
||||
plugin: entity:aggregator_feed
|
|
@ -0,0 +1,23 @@
|
|||
id: d7_aggregator_item
|
||||
label: Aggregator items
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
source:
|
||||
plugin: aggregator_item
|
||||
process:
|
||||
iid: iid
|
||||
fid:
|
||||
plugin: migration
|
||||
migration: d7_aggregator_feed
|
||||
source: fid
|
||||
title: title
|
||||
link: link
|
||||
author: author
|
||||
description: description
|
||||
timestamp: timestamp
|
||||
guid: guid
|
||||
destination:
|
||||
plugin: entity:aggregator_item
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_aggregator_feed
|
|
@ -0,0 +1,25 @@
|
|||
id: d7_aggregator_settings
|
||||
label: Aggregator configuration
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- aggregator_fetcher
|
||||
- aggregator_parser
|
||||
- aggregator_processors
|
||||
- aggregator_allowed_html_tags
|
||||
- aggregator_teaser_length
|
||||
- aggregator_clear
|
||||
- aggregator_summary_items
|
||||
process:
|
||||
fetcher: aggregator_fetcher
|
||||
parser: aggregator_parser
|
||||
processors: aggregator_processors
|
||||
'items/allowed_html': aggregator_allowed_html_tags
|
||||
'items/teaser_length': aggregator_teaser_length
|
||||
'items/expire': aggregator_clear
|
||||
'source/list_max': aggregator_summary_items
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: aggregator.settings
|
51
web/core/modules/aggregator/src/AggregatorFeedViewsData.php
Normal file
51
web/core/modules/aggregator/src/AggregatorFeedViewsData.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\views\EntityViewsData;
|
||||
|
||||
/**
|
||||
* Provides the views data for the aggregator feed entity type.
|
||||
*/
|
||||
class AggregatorFeedViewsData extends EntityViewsData {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewsData() {
|
||||
$data = parent::getViewsData();
|
||||
|
||||
$data['aggregator_feed']['table']['join'] = array(
|
||||
'aggregator_item' => array(
|
||||
'left_field' => 'fid',
|
||||
'field' => 'fid',
|
||||
),
|
||||
);
|
||||
|
||||
$data['aggregator_feed']['fid']['help'] = $this->t('The unique ID of the aggregator feed.');
|
||||
$data['aggregator_feed']['fid']['argument']['id'] = 'aggregator_fid';
|
||||
$data['aggregator_feed']['fid']['argument']['name field'] = 'title';
|
||||
$data['aggregator_feed']['fid']['argument']['numeric'] = TRUE;
|
||||
|
||||
$data['aggregator_feed']['fid']['filter']['id'] = 'numeric';
|
||||
|
||||
$data['aggregator_feed']['title']['help'] = $this->t('The title of the aggregator feed.');
|
||||
$data['aggregator_feed']['title']['field']['default_formatter'] = 'aggregator_title';
|
||||
|
||||
$data['aggregator_feed']['argument']['id'] = 'string';
|
||||
|
||||
$data['aggregator_feed']['url']['help'] = $this->t('The fully-qualified URL of the feed.');
|
||||
|
||||
$data['aggregator_feed']['link']['help'] = $this->t('The link to the source URL of the feed.');
|
||||
|
||||
$data['aggregator_feed']['checked']['help'] = $this->t('The date the feed was last checked for new content.');
|
||||
|
||||
$data['aggregator_feed']['description']['help'] = $this->t('The description of the aggregator feed.');
|
||||
$data['aggregator_feed']['description']['field']['click sortable'] = FALSE;
|
||||
|
||||
$data['aggregator_feed']['modified']['help'] = $this->t('The date of the most recent new content on the feed.');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
45
web/core/modules/aggregator/src/AggregatorItemViewsData.php
Normal file
45
web/core/modules/aggregator/src/AggregatorItemViewsData.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\views\EntityViewsData;
|
||||
|
||||
/**
|
||||
* Provides the views data for the aggregator item entity type.
|
||||
*/
|
||||
class AggregatorItemViewsData extends EntityViewsData {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewsData() {
|
||||
$data = parent::getViewsData();
|
||||
|
||||
$data['aggregator_item']['table']['base']['help'] = $this->t('Aggregator items are imported from external RSS and Atom news feeds.');
|
||||
|
||||
$data['aggregator_item']['iid']['help'] = $this->t('The unique ID of the aggregator item.');
|
||||
$data['aggregator_item']['iid']['argument']['id'] = 'aggregator_iid';
|
||||
$data['aggregator_item']['iid']['argument']['name field'] = 'title';
|
||||
$data['aggregator_item']['iid']['argument']['numeric'] = TRUE;
|
||||
|
||||
$data['aggregator_item']['title']['help'] = $this->t('The title of the aggregator item.');
|
||||
$data['aggregator_item']['title']['field']['default_formatter'] = 'aggregator_title';
|
||||
|
||||
$data['aggregator_item']['link']['help'] = $this->t('The link to the original source URL of the item.');
|
||||
|
||||
$data['aggregator_item']['author']['help'] = $this->t('The author of the original imported item.');
|
||||
|
||||
$data['aggregator_item']['author']['field']['default_formatter'] = 'aggregator_xss';
|
||||
|
||||
$data['aggregator_item']['guid']['help'] = $this->t('The guid of the original imported item.');
|
||||
|
||||
$data['aggregator_item']['description']['help'] = $this->t('The actual content of the imported item.');
|
||||
$data['aggregator_item']['description']['field']['default_formatter'] = 'aggregator_xss';
|
||||
$data['aggregator_item']['description']['field']['click sortable'] = FALSE;
|
||||
|
||||
$data['aggregator_item']['timestamp']['help'] = $this->t('The date the original feed item was posted. (With some feeds, this will be the date it was imported.)');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Plugin annotation object for aggregator fetcher plugins.
|
||||
*
|
||||
* Plugin Namespace: Plugin\aggregator\fetcher
|
||||
*
|
||||
* For a working example, see \Drupal\aggregator\Plugin\aggregator\fetcher\DefaultFetcher
|
||||
*
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see \Drupal\aggregator\Plugin\FetcherInterface
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class AggregatorFetcher extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The title of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The description of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $description;
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Plugin annotation object for aggregator parser plugins.
|
||||
*
|
||||
* Plugin Namespace: Plugin\aggregator\parser
|
||||
*
|
||||
* For a working example, see \Drupal\aggregator\Plugin\aggregator\parser\DefaultParser
|
||||
*
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see \Drupal\aggregator\Plugin\ParserInterface
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class AggregatorParser extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The title of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The description of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $description;
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Plugin annotation object for aggregator processor plugins.
|
||||
*
|
||||
* Plugin Namespace: Plugin\aggregator\processor
|
||||
*
|
||||
* For a working example, see \Drupal\aggregator\Plugin\aggregator\processor\DefaultProcessor
|
||||
*
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see \Drupal\aggregator\Plugin\ProcessorInterface
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class AggregatorProcessor extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The title of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The description of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $description;
|
||||
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Controller;
|
||||
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Returns responses for aggregator module routes.
|
||||
*/
|
||||
class AggregatorController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\aggregator\Controller\AggregatorController object.
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
*/
|
||||
public function __construct(DateFormatterInterface $date_formatter) {
|
||||
$this->dateFormatter = $date_formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('date.formatter')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the aggregator feed creation form.
|
||||
*
|
||||
* @return array
|
||||
* A form array as expected by drupal_render().
|
||||
*/
|
||||
public function feedAdd() {
|
||||
$feed = $this->entityManager()->getStorage('aggregator_feed')
|
||||
->create(array(
|
||||
'refresh' => 3600,
|
||||
));
|
||||
return $this->entityFormBuilder()->getForm($feed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a listing of aggregator feed items.
|
||||
*
|
||||
* @param \Drupal\aggregator\ItemInterface[] $items
|
||||
* The items to be listed.
|
||||
* @param array|string $feed_source
|
||||
* The feed source URL.
|
||||
*
|
||||
* @return array
|
||||
* The rendered list of items for the feed.
|
||||
*/
|
||||
protected function buildPageList(array $items, $feed_source = '') {
|
||||
// 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'] = $this->entityManager()->getViewBuilder('aggregator_item')
|
||||
->viewMultiple($items, 'default');
|
||||
$build['pager'] = array('#type' => 'pager');
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes a feed, then redirects to the overview page.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $aggregator_feed
|
||||
* An object describing the feed to be refreshed.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* A redirection to the admin overview page.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
* If the query token is missing or invalid.
|
||||
*/
|
||||
public function feedRefresh(FeedInterface $aggregator_feed) {
|
||||
$message = $aggregator_feed->refreshItems()
|
||||
? $this->t('There is new syndicated content from %site.', array('%site' => $aggregator_feed->label()))
|
||||
: $this->t('There is no new syndicated content from %site.', array('%site' => $aggregator_feed->label()));
|
||||
drupal_set_message($message);
|
||||
return $this->redirect('aggregator.admin_overview');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the aggregator administration page.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by drupal_render().
|
||||
*/
|
||||
public function adminOverview() {
|
||||
$entity_manager = $this->entityManager();
|
||||
$feeds = $entity_manager->getStorage('aggregator_feed')
|
||||
->loadMultiple();
|
||||
|
||||
$header = array($this->t('Title'), $this->t('Items'), $this->t('Last update'), $this->t('Next update'), $this->t('Operations'));
|
||||
$rows = array();
|
||||
/** @var \Drupal\aggregator\FeedInterface[] $feeds */
|
||||
foreach ($feeds as $feed) {
|
||||
$row = array();
|
||||
$row[] = $feed->link();
|
||||
$row[] = $this->formatPlural($entity_manager->getStorage('aggregator_item')->getItemCount($feed), '1 item', '@count items');
|
||||
$last_checked = $feed->getLastCheckedTime();
|
||||
$refresh_rate = $feed->getRefreshRate();
|
||||
|
||||
$row[] = ($last_checked ? $this->t('@time ago', array('@time' => $this->dateFormatter->formatInterval(REQUEST_TIME - $last_checked))) : $this->t('never'));
|
||||
if (!$last_checked && $refresh_rate) {
|
||||
$next_update = $this->t('imminently');
|
||||
}
|
||||
elseif ($last_checked && $refresh_rate) {
|
||||
$next_update = $next = $this->t('%time left', array('%time' => $this->dateFormatter->formatInterval($last_checked + $refresh_rate - REQUEST_TIME)));
|
||||
}
|
||||
else {
|
||||
$next_update = $this->t('never');
|
||||
}
|
||||
$row[] = $next_update;
|
||||
$links['edit'] = [
|
||||
'title' => $this->t('Edit'),
|
||||
'url' => Url::fromRoute('entity.aggregator_feed.edit_form', ['aggregator_feed' => $feed->id()]),
|
||||
];
|
||||
$links['delete'] = array(
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => Url::fromRoute('entity.aggregator_feed.delete_form', ['aggregator_feed' => $feed->id()]),
|
||||
);
|
||||
$links['delete_items'] = array(
|
||||
'title' => $this->t('Delete items'),
|
||||
'url' => Url::fromRoute('aggregator.feed_items_delete', ['aggregator_feed' => $feed->id()]),
|
||||
);
|
||||
$links['update'] = array(
|
||||
'title' => $this->t('Update items'),
|
||||
'url' => Url::fromRoute('aggregator.feed_refresh', ['aggregator_feed' => $feed->id()]),
|
||||
);
|
||||
$row[] = array(
|
||||
'data' => array(
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
),
|
||||
);
|
||||
$rows[] = $row;
|
||||
}
|
||||
$build['feeds'] = array(
|
||||
'#prefix' => '<h3>' . $this->t('Feed overview') . '</h3>',
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#empty' => $this->t('No feeds available. <a href=":link">Add feed</a>.', array(':link' => $this->url('aggregator.feed_add'))),
|
||||
);
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the most recent items gathered from any feed.
|
||||
*
|
||||
* @return string
|
||||
* The rendered list of items for the feed.
|
||||
*/
|
||||
public function pageLast() {
|
||||
$items = $this->entityManager()->getStorage('aggregator_item')->loadAll(20);
|
||||
$build = $this->buildPageList($items);
|
||||
$build['#attached']['feed'][] = array('aggregator/rss', $this->config('system.site')->get('name') . ' ' . $this->t('aggregator'));
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route title callback.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $aggregator_feed
|
||||
* The aggregator feed.
|
||||
*
|
||||
* @return array
|
||||
* The feed label as a render array.
|
||||
*/
|
||||
public function feedTitle(FeedInterface $aggregator_feed) {
|
||||
return ['#markup' => $aggregator_feed->label(), '#allowed_tags' => Xss::getHtmlTagList()];
|
||||
}
|
||||
|
||||
}
|
389
web/core/modules/aggregator/src/Entity/Feed.php
Normal file
389
web/core/modules/aggregator/src/Entity/Feed.php
Normal file
|
@ -0,0 +1,389 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Entity;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines the aggregator feed entity class.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "aggregator_feed",
|
||||
* label = @Translation("Aggregator feed"),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\aggregator\FeedStorage",
|
||||
* "storage_schema" = "Drupal\aggregator\FeedStorageSchema",
|
||||
* "view_builder" = "Drupal\aggregator\FeedViewBuilder",
|
||||
* "access" = "Drupal\aggregator\FeedAccessControlHandler",
|
||||
* "views_data" = "Drupal\aggregator\AggregatorFeedViewsData",
|
||||
* "form" = {
|
||||
* "default" = "Drupal\aggregator\FeedForm",
|
||||
* "delete" = "Drupal\aggregator\Form\FeedDeleteForm",
|
||||
* "delete_items" = "Drupal\aggregator\Form\FeedItemsDeleteForm",
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\aggregator\FeedHtmlRouteProvider",
|
||||
* },
|
||||
* },
|
||||
* links = {
|
||||
* "canonical" = "/aggregator/sources/{aggregator_feed}",
|
||||
* "edit-form" = "/aggregator/sources/{aggregator_feed}/configure",
|
||||
* "delete-form" = "/aggregator/sources/{aggregator_feed}/delete",
|
||||
* },
|
||||
* field_ui_base_route = "aggregator.admin_overview",
|
||||
* base_table = "aggregator_feed",
|
||||
* render_cache = FALSE,
|
||||
* entity_keys = {
|
||||
* "id" = "fid",
|
||||
* "label" = "title",
|
||||
* "langcode" = "langcode",
|
||||
* "uuid" = "uuid",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Feed extends ContentEntityBase implements FeedInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->get('title')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems() {
|
||||
\Drupal::service('aggregator.items.importer')->delete($this);
|
||||
|
||||
// Reset feed.
|
||||
$this->setLastCheckedTime(0);
|
||||
$this->setHash('');
|
||||
$this->setEtag('');
|
||||
$this->setLastModified(0);
|
||||
$this->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function refreshItems() {
|
||||
$success = \Drupal::service('aggregator.items.importer')->refresh($this);
|
||||
|
||||
// Regardless of successful or not, indicate that it has been checked.
|
||||
$this->setLastCheckedTime(REQUEST_TIME);
|
||||
$this->setQueuedTime(0);
|
||||
$this->save();
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function preCreate(EntityStorageInterface $storage, array &$values) {
|
||||
$values += array(
|
||||
'link' => '',
|
||||
'description' => '',
|
||||
'image' => '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function preDelete(EntityStorageInterface $storage, array $entities) {
|
||||
foreach ($entities as $entity) {
|
||||
// Notify processors to delete stored items.
|
||||
\Drupal::service('aggregator.items.importer')->delete($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
if (\Drupal::moduleHandler()->moduleExists('block')) {
|
||||
// Make sure there are no active blocks for these feeds.
|
||||
$ids = \Drupal::entityQuery('block')
|
||||
->condition('plugin', 'aggregator_feed_block')
|
||||
->condition('settings.feed', array_keys($entities))
|
||||
->execute();
|
||||
if ($ids) {
|
||||
$block_storage = \Drupal::entityManager()->getStorage('block');
|
||||
$block_storage->delete($block_storage->loadMultiple($ids));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['fid']->setLabel(t('Feed ID'))
|
||||
->setDescription(t('The ID of the aggregator feed.'));
|
||||
|
||||
$fields['uuid']->setDescription(t('The aggregator feed UUID.'));
|
||||
|
||||
$fields['langcode']->setLabel(t('Language code'))
|
||||
->setDescription(t('The feed language code.'));
|
||||
|
||||
$fields['title'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Title'))
|
||||
->setDescription(t('The name of the feed (or the name of the website providing the feed).'))
|
||||
->setRequired(TRUE)
|
||||
->setSetting('max_length', 255)
|
||||
->setDisplayOptions('form', array(
|
||||
'type' => 'string_textfield',
|
||||
'weight' => -5,
|
||||
))
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->addConstraint('FeedTitle');
|
||||
|
||||
$fields['url'] = BaseFieldDefinition::create('uri')
|
||||
->setLabel(t('URL'))
|
||||
->setDescription(t('The fully-qualified URL of the feed.'))
|
||||
->setRequired(TRUE)
|
||||
->setDisplayOptions('form', array(
|
||||
'type' => 'uri',
|
||||
'weight' => -3,
|
||||
))
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->addConstraint('FeedUrl');
|
||||
|
||||
$intervals = array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200);
|
||||
$period = array_map(array(\Drupal::service('date.formatter'), 'formatInterval'), array_combine($intervals, $intervals));
|
||||
$period[AGGREGATOR_CLEAR_NEVER] = t('Never');
|
||||
|
||||
$fields['refresh'] = BaseFieldDefinition::create('list_integer')
|
||||
->setLabel(t('Update interval'))
|
||||
->setDescription(t('The length of time between feed updates. Requires a correctly configured cron maintenance task.'))
|
||||
->setSetting('unsigned', TRUE)
|
||||
->setRequired(TRUE)
|
||||
->setSetting('allowed_values', $period)
|
||||
->setDisplayOptions('form', array(
|
||||
'type' => 'options_select',
|
||||
'weight' => -2,
|
||||
))
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['checked'] = BaseFieldDefinition::create('timestamp')
|
||||
->setLabel(t('Checked'))
|
||||
->setDescription(t('Last time feed was checked for new items, as Unix timestamp.'))
|
||||
->setDefaultValue(0)
|
||||
->setDisplayOptions('view', array(
|
||||
'label' => 'inline',
|
||||
'type' => 'timestamp_ago',
|
||||
'weight' => 1,
|
||||
))
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
$fields['queued'] = BaseFieldDefinition::create('timestamp')
|
||||
->setLabel(t('Queued'))
|
||||
->setDescription(t('Time when this feed was queued for refresh, 0 if not queued.'))
|
||||
->setDefaultValue(0);
|
||||
|
||||
$fields['link'] = BaseFieldDefinition::create('uri')
|
||||
->setLabel(t('URL'))
|
||||
->setDescription(t('The link of the feed.'))
|
||||
->setDisplayOptions('view', array(
|
||||
'label' => 'inline',
|
||||
'weight' => 4,
|
||||
))
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
$fields['description'] = BaseFieldDefinition::create('string_long')
|
||||
->setLabel(t('Description'))
|
||||
->setDescription(t("The parent website's description that comes from the @description element in the feed.", array('@description' => '<description>')));
|
||||
|
||||
$fields['image'] = BaseFieldDefinition::create('uri')
|
||||
->setLabel(t('Image'))
|
||||
->setDescription(t('An image representing the feed.'));
|
||||
|
||||
$fields['hash'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Hash'))
|
||||
->setSetting('is_ascii', TRUE)
|
||||
->setDescription(t('Calculated hash of the feed data, used for validating cache.'));
|
||||
|
||||
$fields['etag'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Etag'))
|
||||
->setDescription(t('Entity tag HTTP response header, used for validating cache.'));
|
||||
|
||||
// This is updated by the fetcher and not when the feed is saved, therefore
|
||||
// it's a timestamp and not a changed field.
|
||||
$fields['modified'] = BaseFieldDefinition::create('timestamp')
|
||||
->setLabel(t('Modified'))
|
||||
->setDescription(t('When the feed was last modified, as a Unix timestamp.'));
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getUrl() {
|
||||
return $this->get('url')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRefreshRate() {
|
||||
return $this->get('refresh')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLastCheckedTime() {
|
||||
return $this->get('checked')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQueuedTime() {
|
||||
return $this->get('queued')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getWebsiteUrl() {
|
||||
return $this->get('link')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->get('description')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getImage() {
|
||||
return $this->get('image')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHash() {
|
||||
return $this->get('hash')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getEtag() {
|
||||
return $this->get('etag')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLastModified() {
|
||||
return $this->get('modified')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTitle($title) {
|
||||
$this->set('title', $title);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUrl($url) {
|
||||
$this->set('url', $url);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setRefreshRate($refresh) {
|
||||
$this->set('refresh', $refresh);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLastCheckedTime($checked) {
|
||||
$this->set('checked', $checked);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setQueuedTime($queued) {
|
||||
$this->set('queued', $queued);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setWebsiteUrl($link) {
|
||||
$this->set('link', $link);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDescription($description) {
|
||||
$this->set('description', $description);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setImage($image) {
|
||||
$this->set('image', $image);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setHash($hash) {
|
||||
$this->set('hash', $hash);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setEtag($etag) {
|
||||
$this->set('etag', $etag);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLastModified($modified) {
|
||||
$this->set('modified', $modified);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
243
web/core/modules/aggregator/src/Entity/Item.php
Normal file
243
web/core/modules/aggregator/src/Entity/Item.php
Normal file
|
@ -0,0 +1,243 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Entity;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\aggregator\ItemInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Defines the aggregator item entity class.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "aggregator_item",
|
||||
* label = @Translation("Aggregator feed item"),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\aggregator\ItemStorage",
|
||||
* "storage_schema" = "Drupal\aggregator\ItemStorageSchema",
|
||||
* "view_builder" = "Drupal\aggregator\ItemViewBuilder",
|
||||
* "access" = "Drupal\aggregator\FeedAccessControlHandler",
|
||||
* "views_data" = "Drupal\aggregator\AggregatorItemViewsData"
|
||||
* },
|
||||
* uri_callback = "Drupal\aggregator\Entity\Item::buildUri",
|
||||
* base_table = "aggregator_item",
|
||||
* render_cache = FALSE,
|
||||
* list_cache_tags = { "aggregator_feed_list" },
|
||||
* entity_keys = {
|
||||
* "id" = "iid",
|
||||
* "label" = "title",
|
||||
* "langcode" = "langcode",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Item extends ContentEntityBase implements ItemInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->get('title')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['iid']->setLabel(t('Aggregator item ID'))
|
||||
->setDescription(t('The ID of the feed item.'));
|
||||
|
||||
$fields['langcode']->setLabel(t('Language code'))
|
||||
->setDescription(t('The feed item language code.'));
|
||||
|
||||
$fields['fid'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('Source feed'))
|
||||
->setRequired(TRUE)
|
||||
->setDescription(t('The aggregator feed entity associated with this item.'))
|
||||
->setSetting('target_type', 'aggregator_feed')
|
||||
->setDisplayOptions('view', array(
|
||||
'label' => 'hidden',
|
||||
'type' => 'entity_reference_label',
|
||||
'weight' => 0,
|
||||
))
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['title'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Title'))
|
||||
->setDescription(t('The title of the feed item.'));
|
||||
|
||||
$fields['link'] = BaseFieldDefinition::create('uri')
|
||||
->setLabel(t('Link'))
|
||||
->setDescription(t('The link of the feed item.'))
|
||||
->setDisplayOptions('view', array(
|
||||
'type' => 'hidden',
|
||||
))
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
$fields['author'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Author'))
|
||||
->setDescription(t('The author of the feed item.'))
|
||||
->setDisplayOptions('view', array(
|
||||
'label' => 'hidden',
|
||||
'weight' => 3,
|
||||
))
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
$fields['description'] = BaseFieldDefinition::create('string_long')
|
||||
->setLabel(t('Description'))
|
||||
->setDescription(t('The body of the feed item.'));
|
||||
|
||||
$fields['timestamp'] = BaseFieldDefinition::create('created')
|
||||
->setLabel(t('Posted on'))
|
||||
->setDescription(t('Posted date of the feed item, as a Unix timestamp.'))
|
||||
->setDisplayOptions('view', array(
|
||||
'label' => 'hidden',
|
||||
'type' => 'timestamp_ago',
|
||||
'weight' => 1,
|
||||
))
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
// @todo Convert to a real UUID field in
|
||||
// https://www.drupal.org/node/2149851.
|
||||
$fields['guid'] = BaseFieldDefinition::create('string_long')
|
||||
->setLabel(t('GUID'))
|
||||
->setDescription(t('Unique identifier for the feed item.'));
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFeedId() {
|
||||
return $this->get('fid')->target_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFeedId($fid) {
|
||||
return $this->set('fid', $fid);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle() {
|
||||
return $this->get('title')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTitle($title) {
|
||||
return $this->set('title', $title);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLink() {
|
||||
return $this->get('link')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLink($link) {
|
||||
return $this->set('link', $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthor() {
|
||||
return $this->get('author')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAuthor($author) {
|
||||
return $this->set('author', $author);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->get('description')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDescription($description) {
|
||||
return $this->set('description', $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPostedTime() {
|
||||
return $this->get('timestamp')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPostedTime($timestamp) {
|
||||
return $this->set('timestamp', $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGuid() {
|
||||
return $this->get('guid')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setGuid($guid) {
|
||||
return $this->set('guid', $guid);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||
parent::postSave($storage, $update);
|
||||
|
||||
// Entity::postSave() calls Entity::invalidateTagsOnSave(), which only
|
||||
// handles the regular cases. The Item entity has one special case: a newly
|
||||
// created Item is *also* associated with a Feed, so we must invalidate the
|
||||
// associated Feed's cache tag.
|
||||
if (!$update) {
|
||||
Cache::invalidateTags($this->getCacheTagsToInvalidate());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTagsToInvalidate() {
|
||||
return Feed::load($this->getFeedId())->getCacheTags();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Entity URI callback.
|
||||
*/
|
||||
public static function buildUri(ItemInterface $item) {
|
||||
return Url::fromUri($item->getLink());
|
||||
}
|
||||
|
||||
}
|
37
web/core/modules/aggregator/src/FeedAccessControlHandler.php
Normal file
37
web/core/modules/aggregator/src/FeedAccessControlHandler.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines an access control handler for the feed entity.
|
||||
*
|
||||
* @see \Drupal\aggregator\Entity\Feed
|
||||
*/
|
||||
class FeedAccessControlHandler extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return AccessResult::allowedIfHasPermission($account, 'access news feeds');
|
||||
|
||||
default:
|
||||
return AccessResult::allowedIfHasPermission($account, 'administer news feeds');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
return AccessResult::allowedIfHasPermission($account, 'administer news feeds');
|
||||
}
|
||||
|
||||
}
|
32
web/core/modules/aggregator/src/FeedForm.php
Normal file
32
web/core/modules/aggregator/src/FeedForm.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Form handler for the aggregator feed edit forms.
|
||||
*/
|
||||
class FeedForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$feed = $this->entity;
|
||||
$status = $feed->save();
|
||||
$label = $feed->label();
|
||||
$view_link = $feed->link($label, 'canonical');
|
||||
if ($status == SAVED_UPDATED) {
|
||||
drupal_set_message($this->t('The feed %feed has been updated.', array('%feed' => $view_link)));
|
||||
$form_state->setRedirectUrl($feed->urlInfo('canonical'));
|
||||
}
|
||||
else {
|
||||
$this->logger('aggregator')->notice('Feed %feed added.', array('%feed' => $feed->label(), 'link' => $this->l($this->t('View'), new Url('aggregator.admin_overview'))));
|
||||
drupal_set_message($this->t('The feed %feed has been added.', array('%feed' => $view_link)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
34
web/core/modules/aggregator/src/FeedHtmlRouteProvider.php
Normal file
34
web/core/modules/aggregator/src/FeedHtmlRouteProvider.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
|
||||
|
||||
/**
|
||||
* Provides HTML routes for the feed entity type.
|
||||
*/
|
||||
class FeedHtmlRouteProvider extends AdminHtmlRouteProvider {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
|
||||
$route = parent::getCanonicalRoute($entity_type);
|
||||
$route->setDefault('_title_controller', '\Drupal\aggregator\Controller\AggregatorController::feedTitle');
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditFormRoute(EntityTypeInterface $entity_type) {
|
||||
$route = parent::getEditFormRoute($entity_type);
|
||||
|
||||
$route->setDefault('_title', 'Configure');
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
239
web/core/modules/aggregator/src/FeedInterface.php
Normal file
239
web/core/modules/aggregator/src/FeedInterface.php
Normal file
|
@ -0,0 +1,239 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining an aggregator feed entity.
|
||||
*/
|
||||
interface FeedInterface extends ContentEntityInterface {
|
||||
|
||||
/**
|
||||
* Sets the title of the feed.
|
||||
*
|
||||
* @param string $title
|
||||
* The short title of the feed.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setTitle($title);
|
||||
|
||||
/**
|
||||
* Returns the url to the feed.
|
||||
*
|
||||
* @return string
|
||||
* The url to the feed.
|
||||
*/
|
||||
public function getUrl();
|
||||
|
||||
/**
|
||||
* Sets the url to the feed.
|
||||
*
|
||||
* @param string $url
|
||||
* A string containing the url of the feed.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setUrl($url);
|
||||
|
||||
/**
|
||||
* Returns the refresh rate of the feed in seconds.
|
||||
*
|
||||
* @return int
|
||||
* The refresh rate of the feed in seconds.
|
||||
*/
|
||||
public function getRefreshRate();
|
||||
|
||||
/**
|
||||
* Sets the refresh rate of the feed in seconds.
|
||||
*
|
||||
* @param int $refresh
|
||||
* The refresh rate of the feed in seconds.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setRefreshRate($refresh);
|
||||
|
||||
/**
|
||||
* Returns the last time where the feed was checked for new items.
|
||||
*
|
||||
* @return int
|
||||
* The timestamp when new items were last checked for.
|
||||
*/
|
||||
public function getLastCheckedTime();
|
||||
|
||||
/**
|
||||
* Sets the time when this feed was queued for refresh, 0 if not queued.
|
||||
*
|
||||
* @param int $checked
|
||||
* The timestamp of the last refresh.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setLastCheckedTime($checked);
|
||||
|
||||
/**
|
||||
* Returns the time when this feed was queued for refresh, 0 if not queued.
|
||||
*
|
||||
* @return int
|
||||
* The timestamp of the last refresh.
|
||||
*/
|
||||
public function getQueuedTime();
|
||||
|
||||
/**
|
||||
* Sets the time when this feed was queued for refresh, 0 if not queued.
|
||||
*
|
||||
* @param int $queued
|
||||
* The timestamp of the last refresh.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setQueuedTime($queued);
|
||||
|
||||
/**
|
||||
* Returns the parent website of the feed.
|
||||
*
|
||||
* @return string
|
||||
* The parent website of the feed.
|
||||
*/
|
||||
public function getWebsiteUrl();
|
||||
|
||||
/**
|
||||
* Sets the parent website of the feed.
|
||||
*
|
||||
* @param string $link
|
||||
* A string containing the parent website of the feed.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setWebsiteUrl($link);
|
||||
|
||||
/**
|
||||
* Returns the description of the feed.
|
||||
*
|
||||
* @return string
|
||||
* The description of the feed.
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Sets the description of the feed.
|
||||
*
|
||||
* @param string $description
|
||||
* The description of the feed.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setDescription($description);
|
||||
|
||||
/**
|
||||
* Returns the primary image attached to the feed.
|
||||
*
|
||||
* @return string
|
||||
* The URL of the primary image attached to the feed.
|
||||
*/
|
||||
public function getImage();
|
||||
|
||||
/**
|
||||
* Sets the primary image attached to the feed.
|
||||
*
|
||||
* @param string $image
|
||||
* An image URL.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setImage($image);
|
||||
|
||||
/**
|
||||
* Returns the calculated hash of the feed data, used for validating cache.
|
||||
*
|
||||
* @return string
|
||||
* The calculated hash of the feed data.
|
||||
*/
|
||||
public function getHash();
|
||||
|
||||
/**
|
||||
* Sets the calculated hash of the feed data, used for validating cache.
|
||||
*
|
||||
* @param string $hash
|
||||
* A string containing the calculated hash of the feed. Must contain
|
||||
* US ASCII characters only.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setHash($hash);
|
||||
|
||||
/**
|
||||
* Returns the entity tag HTTP response header, used for validating cache.
|
||||
*
|
||||
* @return string
|
||||
* The entity tag HTTP response header.
|
||||
*/
|
||||
public function getEtag();
|
||||
|
||||
/**
|
||||
* Sets the entity tag HTTP response header, used for validating cache.
|
||||
*
|
||||
* @param string $etag
|
||||
* A string containing the entity tag HTTP response header.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setEtag($etag);
|
||||
|
||||
/**
|
||||
* Return when the feed was modified last time.
|
||||
*
|
||||
* @return int
|
||||
* The timestamp of the last time the feed was modified.
|
||||
*/
|
||||
public function getLastModified();
|
||||
|
||||
/**
|
||||
* Sets the last modification of the feed.
|
||||
*
|
||||
* @param int $modified
|
||||
* The timestamp when the feed was modified.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setLastModified($modified);
|
||||
|
||||
/**
|
||||
* Deletes all items from a feed.
|
||||
*
|
||||
* This will also reset the last checked and modified time of the feed and
|
||||
* save it.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*
|
||||
* @see \Drupal\aggregator\ItemsImporterInterface::delete()
|
||||
*/
|
||||
public function deleteItems();
|
||||
|
||||
/**
|
||||
* Updates the feed items by triggering the import process.
|
||||
*
|
||||
* This will also update the last checked time of the feed and save it.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there is new content for the feed FALSE otherwise.
|
||||
*
|
||||
* @see \Drupal\aggregator\ItemsImporterInterface::refresh()
|
||||
*/
|
||||
public function refreshItems();
|
||||
|
||||
}
|
25
web/core/modules/aggregator/src/FeedStorage.php
Normal file
25
web/core/modules/aggregator/src/FeedStorage.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
|
||||
/**
|
||||
* Controller class for aggregator's feeds.
|
||||
*
|
||||
* This extends the Drupal\Core\Entity\Sql\SqlContentEntityStorage class, adding
|
||||
* required special handling for feed entities.
|
||||
*/
|
||||
class FeedStorage extends SqlContentEntityStorage implements FeedStorageInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFeedIdsToRefresh() {
|
||||
return $this->database->query('SELECT fid FROM {aggregator_feed} WHERE queued = 0 AND checked + refresh < :time AND refresh <> :never', array(
|
||||
':time' => REQUEST_TIME,
|
||||
':never' => AGGREGATOR_CLEAR_NEVER,
|
||||
))->fetchCol();
|
||||
}
|
||||
|
||||
}
|
20
web/core/modules/aggregator/src/FeedStorageInterface.php
Normal file
20
web/core/modules/aggregator/src/FeedStorageInterface.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator feed entity storage classes.
|
||||
*/
|
||||
interface FeedStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Returns the fids of feeds that need to be refreshed.
|
||||
*
|
||||
* @return array
|
||||
* A list of feed ids to be refreshed.
|
||||
*/
|
||||
public function getFeedIdsToRefresh();
|
||||
|
||||
}
|
39
web/core/modules/aggregator/src/FeedStorageSchema.php
Normal file
39
web/core/modules/aggregator/src/FeedStorageSchema.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Defines the feed schema handler.
|
||||
*/
|
||||
class FeedStorageSchema extends SqlContentEntityStorageSchema {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
|
||||
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
|
||||
$field_name = $storage_definition->getName();
|
||||
|
||||
if ($table_name == 'aggregator_feed') {
|
||||
switch ($field_name) {
|
||||
case 'url':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE, 255);
|
||||
break;
|
||||
|
||||
case 'queued':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
|
||||
break;
|
||||
|
||||
case 'title':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
135
web/core/modules/aggregator/src/FeedViewBuilder.php
Normal file
135
web/core/modules/aggregator/src/FeedViewBuilder.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* View builder handler for aggregator feeds.
|
||||
*/
|
||||
class FeedViewBuilder extends EntityViewBuilder {
|
||||
|
||||
/**
|
||||
* Constructs a new FeedViewBuilder.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Config\Config $config
|
||||
* The 'aggregator.settings' config.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, Config $config) {
|
||||
parent::__construct($entity_type, $entity_manager, $language_manager);
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity.manager'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('config.factory')->get('aggregator.settings')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildComponents(array &$build, array $entities, array $displays, $view_mode) {
|
||||
parent::buildComponents($build, $entities, $displays, $view_mode);
|
||||
|
||||
foreach ($entities as $id => $entity) {
|
||||
$bundle = $entity->bundle();
|
||||
$display = $displays[$bundle];
|
||||
|
||||
if ($display->getComponent('items')) {
|
||||
// When in summary view mode, respect the list_max setting.
|
||||
$limit = $view_mode == 'summary' ? $this->config->get('source.list_max') : 20;
|
||||
// Retrieve the items attached to this feed.
|
||||
$items = $this->entityManager
|
||||
->getStorage('aggregator_item')
|
||||
->loadByFeed($entity->id(), $limit);
|
||||
|
||||
$build[$id]['items'] = $this->entityManager
|
||||
->getViewBuilder('aggregator_item')
|
||||
->viewMultiple($items, $view_mode, $entity->language()->getId());
|
||||
|
||||
if ($view_mode == 'full') {
|
||||
// Also add the pager.
|
||||
$build[$id]['pager'] = array('#type' => 'pager');
|
||||
}
|
||||
}
|
||||
|
||||
if ($display->getComponent('description')) {
|
||||
$build[$id]['description'] = array(
|
||||
'#markup' => $entity->getDescription(),
|
||||
'#allowed_tags' => _aggregator_allowed_tags(),
|
||||
'#prefix' => '<div class="feed-description">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
}
|
||||
|
||||
if ($display->getComponent('image')) {
|
||||
$image_link = array();
|
||||
// Render the image as link if it is available.
|
||||
$image = $entity->getImage();
|
||||
$label = $entity->label();
|
||||
$link_href = $entity->getWebsiteUrl();
|
||||
if ($image && $label && $link_href) {
|
||||
$link_title = array(
|
||||
'#theme' => 'image',
|
||||
'#uri' => $image,
|
||||
'#alt' => $label,
|
||||
);
|
||||
$image_link = array(
|
||||
'#type' => 'link',
|
||||
'#title' => $link_title,
|
||||
'#url' => Url::fromUri($link_href),
|
||||
'#options' => array(
|
||||
'attributes' => array('class' => array('feed-image')),
|
||||
),
|
||||
);
|
||||
}
|
||||
$build[$id]['image'] = $image_link;
|
||||
}
|
||||
|
||||
if ($display->getComponent('feed_icon')) {
|
||||
$build[$id]['feed_icon'] = array(
|
||||
'#theme' => 'feed_icon',
|
||||
'#url' => $entity->getUrl(),
|
||||
'#title' => t('@title feed', array('@title' => $entity->label())),
|
||||
);
|
||||
}
|
||||
|
||||
if ($display->getComponent('more_link')) {
|
||||
$title_stripped = strip_tags($entity->label());
|
||||
$build[$id]['more_link'] = array(
|
||||
'#type' => 'link',
|
||||
'#title' => t('More<span class="visually-hidden"> posts about @title</span>', array(
|
||||
'@title' => $title_stripped,
|
||||
)),
|
||||
'#url' => Url::fromRoute('entity.aggregator_feed.canonical', ['aggregator_feed' => $entity->id()]),
|
||||
'#options' => array(
|
||||
'attributes' => array(
|
||||
'title' => $title_stripped,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
36
web/core/modules/aggregator/src/Form/FeedDeleteForm.php
Normal file
36
web/core/modules/aggregator/src/Form/FeedDeleteForm.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityDeleteForm;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a form for deleting a feed.
|
||||
*/
|
||||
class FeedDeleteForm extends ContentEntityDeleteForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('aggregator.admin_overview');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getRedirectUrl() {
|
||||
return $this->getCancelUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDeletionMessage() {
|
||||
return $this->t('The feed %label has been deleted.', array(
|
||||
'%label' => $this->entity->label(),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
44
web/core/modules/aggregator/src/Form/FeedItemsDeleteForm.php
Normal file
44
web/core/modules/aggregator/src/Form/FeedItemsDeleteForm.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a deletion confirmation form for items that belong to a feed.
|
||||
*/
|
||||
class FeedItemsDeleteForm extends ContentEntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete all items from the feed %feed?', array('%feed' => $this->entity->label()));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('aggregator.admin_overview');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfirmText() {
|
||||
return $this->t('Delete items');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->entity->deleteItems();
|
||||
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
}
|
209
web/core/modules/aggregator/src/Form/OpmlFeedAdd.php
Normal file
209
web/core/modules/aggregator/src/Form/OpmlFeedAdd.php
Normal file
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Form;
|
||||
|
||||
use Drupal\aggregator\FeedStorageInterface;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
|
||||
/**
|
||||
* Imports feeds from OPML.
|
||||
*/
|
||||
class OpmlFeedAdd extends FormBase {
|
||||
|
||||
/**
|
||||
* The feed storage.
|
||||
*
|
||||
* @var \Drupal\aggregator\FeedStorageInterface
|
||||
*/
|
||||
protected $feedStorage;
|
||||
|
||||
/**
|
||||
* The HTTP client to fetch the feed data with.
|
||||
*
|
||||
* @var \GuzzleHttp\ClientInterface
|
||||
*/
|
||||
protected $httpClient;
|
||||
|
||||
/**
|
||||
* Constructs a database object.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedStorageInterface $feed_storage
|
||||
* The feed storage.
|
||||
* @param \GuzzleHttp\ClientInterface $http_client
|
||||
* The Guzzle HTTP client.
|
||||
*/
|
||||
public function __construct(FeedStorageInterface $feed_storage, ClientInterface $http_client) {
|
||||
$this->feedStorage = $feed_storage;
|
||||
$this->httpClient = $http_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.manager')->getStorage('aggregator_feed'),
|
||||
$container->get('http_client')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'aggregator_opml_add';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$intervals = array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200);
|
||||
$period = array_map(array(\Drupal::service('date.formatter'), 'formatInterval'), array_combine($intervals, $intervals));
|
||||
|
||||
$form['upload'] = array(
|
||||
'#type' => 'file',
|
||||
'#title' => $this->t('OPML File'),
|
||||
'#description' => $this->t('Upload an OPML file containing a list of feeds to be imported.'),
|
||||
);
|
||||
$form['remote'] = array(
|
||||
'#type' => 'url',
|
||||
'#title' => $this->t('OPML Remote URL'),
|
||||
'#maxlength' => 1024,
|
||||
'#description' => $this->t('Enter the URL of an OPML file. This file will be downloaded and processed only once on submission of the form.'),
|
||||
);
|
||||
$form['refresh'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Update interval'),
|
||||
'#default_value' => 3600,
|
||||
'#options' => $period,
|
||||
'#description' => $this->t('The length of time between feed updates. Requires a correctly configured <a href=":cron">cron maintenance task</a>.', array(':cron' => $this->url('system.status'))),
|
||||
);
|
||||
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
$form['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Import'),
|
||||
);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
// If both fields are empty or filled, cancel.
|
||||
$all_files = $this->getRequest()->files->get('files', []);
|
||||
if ($form_state->isValueEmpty('remote') == empty($all_files['upload'])) {
|
||||
$form_state->setErrorByName('remote', $this->t('<em>Either</em> upload a file or enter a URL.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$validators = array('file_validate_extensions' => array('opml xml'));
|
||||
if ($file = file_save_upload('upload', $validators, FALSE, 0)) {
|
||||
$data = file_get_contents($file->getFileUri());
|
||||
}
|
||||
else {
|
||||
// @todo Move this to a fetcher implementation.
|
||||
try {
|
||||
$response = $this->httpClient->get($form_state->getValue('remote'));
|
||||
$data = (string) $response->getBody();
|
||||
}
|
||||
catch (RequestException $e) {
|
||||
$this->logger('aggregator')->warning('Failed to download OPML file due to "%error".', array('%error' => $e->getMessage()));
|
||||
drupal_set_message($this->t('Failed to download OPML file due to "%error".', array('%error' => $e->getMessage())));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$feeds = $this->parseOpml($data);
|
||||
if (empty($feeds)) {
|
||||
drupal_set_message($this->t('No new feed has been added.'));
|
||||
return;
|
||||
}
|
||||
|
||||
// @todo Move this functionality to a processor.
|
||||
foreach ($feeds as $feed) {
|
||||
// Ensure URL is valid.
|
||||
if (!UrlHelper::isValid($feed['url'], TRUE)) {
|
||||
drupal_set_message($this->t('The URL %url is invalid.', array('%url' => $feed['url'])), 'warning');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for duplicate titles or URLs.
|
||||
$query = $this->feedStorage->getQuery();
|
||||
$condition = $query->orConditionGroup()
|
||||
->condition('title', $feed['title'])
|
||||
->condition('url', $feed['url']);
|
||||
$ids = $query
|
||||
->condition($condition)
|
||||
->execute();
|
||||
$result = $this->feedStorage->loadMultiple($ids);
|
||||
foreach ($result as $old) {
|
||||
if (strcasecmp($old->label(), $feed['title']) == 0) {
|
||||
drupal_set_message($this->t('A feed named %title already exists.', array('%title' => $old->label())), 'warning');
|
||||
continue 2;
|
||||
}
|
||||
if (strcasecmp($old->getUrl(), $feed['url']) == 0) {
|
||||
drupal_set_message($this->t('A feed with the URL %url already exists.', array('%url' => $old->getUrl())), 'warning');
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$new_feed = $this->feedStorage->create(array(
|
||||
'title' => $feed['title'],
|
||||
'url' => $feed['url'],
|
||||
'refresh' => $form_state->getValue('refresh'),
|
||||
));
|
||||
$new_feed->save();
|
||||
}
|
||||
|
||||
$form_state->setRedirect('aggregator.admin_overview');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an OPML file.
|
||||
*
|
||||
* Feeds are recognized as <outline> elements with the attributes "text" and
|
||||
* "xmlurl" set.
|
||||
*
|
||||
* @param string $opml
|
||||
* The complete contents of an OPML document.
|
||||
*
|
||||
* @return array
|
||||
* An array of feeds, each an associative array with a "title" and a "url"
|
||||
* element, or NULL if the OPML document failed to be parsed. An empty array
|
||||
* will be returned if the document is valid but contains no feeds, as some
|
||||
* OPML documents do.
|
||||
*
|
||||
* @todo Move this to a parser in https://www.drupal.org/node/1963540.
|
||||
*/
|
||||
protected function parseOpml($opml) {
|
||||
$feeds = array();
|
||||
$xml_parser = drupal_xml_parser_create($opml);
|
||||
if (xml_parse_into_struct($xml_parser, $opml, $values)) {
|
||||
foreach ($values as $entry) {
|
||||
if ($entry['tag'] == 'OUTLINE' && isset($entry['attributes'])) {
|
||||
$item = $entry['attributes'];
|
||||
if (!empty($item['XMLURL']) && !empty($item['TEXT'])) {
|
||||
$feeds[] = array('title' => $item['TEXT'], 'url' => $item['XMLURL']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
xml_parser_free($xml_parser);
|
||||
|
||||
return $feeds;
|
||||
}
|
||||
|
||||
}
|
225
web/core/modules/aggregator/src/Form/SettingsForm.php
Normal file
225
web/core/modules/aggregator/src/Form/SettingsForm.php
Normal file
|
@ -0,0 +1,225 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Form;
|
||||
|
||||
use Drupal\aggregator\Plugin\AggregatorPluginManager;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Drupal\Core\StringTranslation\TranslationInterface;
|
||||
use Drupal\Core\Form\ConfigFormBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Configures aggregator settings for this site.
|
||||
*/
|
||||
class SettingsForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* The aggregator plugin managers.
|
||||
*
|
||||
* @var \Drupal\aggregator\Plugin\AggregatorPluginManager[]
|
||||
*/
|
||||
protected $managers = array();
|
||||
|
||||
/**
|
||||
* The instantiated plugin instances that have configuration forms.
|
||||
*
|
||||
* @var \Drupal\Core\Plugin\PluginFormInterface[]
|
||||
*/
|
||||
protected $configurableInstances = array();
|
||||
|
||||
/**
|
||||
* The aggregator plugin definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $definitions = array(
|
||||
'fetcher' => array(),
|
||||
'parser' => array(),
|
||||
'processor' => array(),
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\aggregator\SettingsForm object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The factory for configuration objects.
|
||||
* @param \Drupal\aggregator\Plugin\AggregatorPluginManager $fetcher_manager
|
||||
* The aggregator fetcher plugin manager.
|
||||
* @param \Drupal\aggregator\Plugin\AggregatorPluginManager $parser_manager
|
||||
* The aggregator parser plugin manager.
|
||||
* @param \Drupal\aggregator\Plugin\AggregatorPluginManager $processor_manager
|
||||
* The aggregator processor plugin manager.
|
||||
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
|
||||
* The string translation manager.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, AggregatorPluginManager $fetcher_manager, AggregatorPluginManager $parser_manager, AggregatorPluginManager $processor_manager, TranslationInterface $string_translation) {
|
||||
parent::__construct($config_factory);
|
||||
$this->stringTranslation = $string_translation;
|
||||
$this->managers = array(
|
||||
'fetcher' => $fetcher_manager,
|
||||
'parser' => $parser_manager,
|
||||
'processor' => $processor_manager,
|
||||
);
|
||||
// Get all available fetcher, parser and processor definitions.
|
||||
foreach (array('fetcher', 'parser', 'processor') as $type) {
|
||||
foreach ($this->managers[$type]->getDefinitions() as $id => $definition) {
|
||||
$this->definitions[$type][$id] = SafeMarkup::format('@title <span class="description">@description</span>', array('@title' => $definition['title'], '@description' => $definition['description']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('config.factory'),
|
||||
$container->get('plugin.manager.aggregator.fetcher'),
|
||||
$container->get('plugin.manager.aggregator.parser'),
|
||||
$container->get('plugin.manager.aggregator.processor'),
|
||||
$container->get('string_translation')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'aggregator_admin_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return ['aggregator.settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->config('aggregator.settings');
|
||||
|
||||
// Global aggregator settings.
|
||||
$form['aggregator_allowed_html_tags'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Allowed HTML tags'),
|
||||
'#size' => 80,
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $config->get('items.allowed_html'),
|
||||
'#description' => $this->t('A space-separated list of HTML tags allowed in the content of feed items. Disallowed tags are stripped from the content.'),
|
||||
);
|
||||
|
||||
// Only show basic configuration if there are actually options.
|
||||
$basic_conf = array();
|
||||
if (count($this->definitions['fetcher']) > 1) {
|
||||
$basic_conf['aggregator_fetcher'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Fetcher'),
|
||||
'#description' => $this->t('Fetchers download data from an external source. Choose a fetcher suitable for the external source you would like to download from.'),
|
||||
'#options' => $this->definitions['fetcher'],
|
||||
'#default_value' => $config->get('fetcher'),
|
||||
);
|
||||
}
|
||||
if (count($this->definitions['parser']) > 1) {
|
||||
$basic_conf['aggregator_parser'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => $this->t('Parser'),
|
||||
'#description' => $this->t('Parsers transform downloaded data into standard structures. Choose a parser suitable for the type of feeds you would like to aggregate.'),
|
||||
'#options' => $this->definitions['parser'],
|
||||
'#default_value' => $config->get('parser'),
|
||||
);
|
||||
}
|
||||
if (count($this->definitions['processor']) > 1) {
|
||||
$basic_conf['aggregator_processors'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('Processors'),
|
||||
'#description' => $this->t('Processors act on parsed feed data, for example they store feed items. Choose the processors suitable for your task.'),
|
||||
'#options' => $this->definitions['processor'],
|
||||
'#default_value' => $config->get('processors'),
|
||||
);
|
||||
}
|
||||
if (count($basic_conf)) {
|
||||
$form['basic_conf'] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Basic configuration'),
|
||||
'#description' => $this->t('For most aggregation tasks, the default settings are fine.'),
|
||||
'#open' => TRUE,
|
||||
);
|
||||
$form['basic_conf'] += $basic_conf;
|
||||
}
|
||||
|
||||
// Call buildConfigurationForm() on the active fetcher and parser.
|
||||
foreach (array('fetcher', 'parser') as $type) {
|
||||
$active = $config->get($type);
|
||||
if (array_key_exists($active, $this->definitions[$type])) {
|
||||
$instance = $this->managers[$type]->createInstance($active);
|
||||
if ($instance instanceof PluginFormInterface) {
|
||||
$form = $instance->buildConfigurationForm($form, $form_state);
|
||||
// Store the instance for validate and submit handlers.
|
||||
// Keying by ID would bring conflicts, because two instances of a
|
||||
// different type could have the same ID.
|
||||
$this->configurableInstances[] = $instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implementing processor plugins will expect an array at $form['processors'].
|
||||
$form['processors'] = array();
|
||||
// Call buildConfigurationForm() for each active processor.
|
||||
foreach ($this->definitions['processor'] as $id => $definition) {
|
||||
if (in_array($id, $config->get('processors'))) {
|
||||
$instance = $this->managers['processor']->createInstance($id);
|
||||
if ($instance instanceof PluginFormInterface) {
|
||||
$form = $instance->buildConfigurationForm($form, $form_state);
|
||||
// Store the instance for validate and submit handlers.
|
||||
// Keying by ID would bring conflicts, because two instances of a
|
||||
// different type could have the same ID.
|
||||
$this->configurableInstances[] = $instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
// Let active plugins validate their settings.
|
||||
foreach ($this->configurableInstances as $instance) {
|
||||
$instance->validateConfigurationForm($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitForm($form, $form_state);
|
||||
$config = $this->config('aggregator.settings');
|
||||
// Let active plugins save their settings.
|
||||
foreach ($this->configurableInstances as $instance) {
|
||||
$instance->submitConfigurationForm($form, $form_state);
|
||||
}
|
||||
|
||||
$config->set('items.allowed_html', $form_state->getValue('aggregator_allowed_html_tags'));
|
||||
if ($form_state->hasValue('aggregator_fetcher')) {
|
||||
$config->set('fetcher', $form_state->getValue('aggregator_fetcher'));
|
||||
}
|
||||
if ($form_state->hasValue('aggregator_parser')) {
|
||||
$config->set('parser', $form_state->getValue('aggregator_parser'));
|
||||
}
|
||||
if ($form_state->hasValue('aggregator_processors')) {
|
||||
$config->set('processors', array_filter($form_state->getValue('aggregator_processors')));
|
||||
}
|
||||
$config->save();
|
||||
}
|
||||
|
||||
}
|
145
web/core/modules/aggregator/src/ItemInterface.php
Normal file
145
web/core/modules/aggregator/src/ItemInterface.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining an aggregator item entity.
|
||||
*/
|
||||
interface ItemInterface extends ContentEntityInterface {
|
||||
|
||||
/**
|
||||
* Returns the feed id of aggregator item.
|
||||
*
|
||||
* @return int
|
||||
* The feed id.
|
||||
*/
|
||||
public function getFeedId();
|
||||
|
||||
/**
|
||||
* Sets the feed id of aggregator item.
|
||||
*
|
||||
* @param int $fid
|
||||
* The feed id.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setFeedId($fid);
|
||||
|
||||
/**
|
||||
* Returns the title of the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The title of the feed item.
|
||||
*/
|
||||
public function getTitle();
|
||||
|
||||
/**
|
||||
* Sets the title of the feed item.
|
||||
*
|
||||
* @param string $title
|
||||
* The title of the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setTitle($title);
|
||||
|
||||
/**
|
||||
* Returns the link to the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The link to the feed item.
|
||||
*/
|
||||
public function getLink();
|
||||
|
||||
/**
|
||||
* Sets the link to the feed item.
|
||||
*
|
||||
* @param string $link
|
||||
* The link to the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setLink($link);
|
||||
|
||||
/**
|
||||
* Returns the author of the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The author of the feed item.
|
||||
*/
|
||||
public function getAuthor();
|
||||
|
||||
/**
|
||||
* Sets the author of the feed item.
|
||||
*
|
||||
* @param string $author
|
||||
* The author name of the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setAuthor($author);
|
||||
|
||||
/**
|
||||
* Returns the body of the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The body of the feed item.
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Sets the body of the feed item.
|
||||
*
|
||||
* @param string $description
|
||||
* The body of the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setDescription($description);
|
||||
|
||||
/**
|
||||
* Returns the posted date of the feed item, as a Unix timestamp.
|
||||
*
|
||||
* @return int
|
||||
* The posted date of the feed item, as a Unix timestamp.
|
||||
*/
|
||||
public function getPostedTime();
|
||||
|
||||
/**
|
||||
* Sets the posted date of the feed item, as a Unix timestamp.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* The posted date of the feed item, as a Unix timestamp.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setPostedTime($timestamp);
|
||||
|
||||
/**
|
||||
* Returns the unique identifier for the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The unique identifier for the feed item.
|
||||
*/
|
||||
public function getGuid();
|
||||
|
||||
/**
|
||||
* Sets the unique identifier for the feed item.
|
||||
*
|
||||
* @param string $guid
|
||||
* The unique identifier for the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setGuid($guid);
|
||||
|
||||
}
|
65
web/core/modules/aggregator/src/ItemStorage.php
Normal file
65
web/core/modules/aggregator/src/ItemStorage.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
|
||||
/**
|
||||
* Controller class for aggregators items.
|
||||
*
|
||||
* This extends the Drupal\Core\Entity\Sql\SqlContentEntityStorage class, adding
|
||||
* required special handling for feed item entities.
|
||||
*/
|
||||
class ItemStorage extends SqlContentEntityStorage implements ItemStorageInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemCount(FeedInterface $feed) {
|
||||
$query = \Drupal::entityQuery('aggregator_item')
|
||||
->condition('fid', $feed->id())
|
||||
->count();
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadAll($limit = NULL) {
|
||||
$query = \Drupal::entityQuery('aggregator_item');
|
||||
return $this->executeFeedItemQuery($query, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadByFeed($fid, $limit = NULL) {
|
||||
$query = \Drupal::entityQuery('aggregator_item')
|
||||
->condition('fid', $fid);
|
||||
return $this->executeFeedItemQuery($query, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to execute an item query.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\Query\QueryInterface $query
|
||||
* The query to execute.
|
||||
* @param int $limit
|
||||
* (optional) The number of items to return.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface[]
|
||||
* An array of the feed items.
|
||||
*/
|
||||
protected function executeFeedItemQuery(QueryInterface $query, $limit) {
|
||||
$query->sort('timestamp', 'DESC')
|
||||
->sort('iid', 'DESC');
|
||||
if (!empty($limit)) {
|
||||
$query->pager($limit);
|
||||
}
|
||||
|
||||
return $this->loadMultiple($query->execute());
|
||||
}
|
||||
|
||||
}
|
47
web/core/modules/aggregator/src/ItemStorageInterface.php
Normal file
47
web/core/modules/aggregator/src/ItemStorageInterface.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator item entity storage classes.
|
||||
*/
|
||||
interface ItemStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Returns the count of the items in a feed.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* The feed entity.
|
||||
*
|
||||
* @return int
|
||||
* The count of items associated with a feed.
|
||||
*/
|
||||
public function getItemCount(FeedInterface $feed);
|
||||
|
||||
/**
|
||||
* Loads feed items from all feeds.
|
||||
*
|
||||
* @param int $limit
|
||||
* (optional) The number of items to return. Defaults to unlimited.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface[]
|
||||
* An array of the feed items.
|
||||
*/
|
||||
public function loadAll($limit = NULL);
|
||||
|
||||
/**
|
||||
* Loads feed items filtered by a feed.
|
||||
*
|
||||
* @param int $fid
|
||||
* The feed ID to filter by.
|
||||
* @param int $limit
|
||||
* (optional) The number of items to return. Defaults to unlimited.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface[]
|
||||
* An array of the feed items.
|
||||
*/
|
||||
public function loadByFeed($fid, $limit = NULL);
|
||||
|
||||
}
|
35
web/core/modules/aggregator/src/ItemStorageSchema.php
Normal file
35
web/core/modules/aggregator/src/ItemStorageSchema.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Defines the item schema handler.
|
||||
*/
|
||||
class ItemStorageSchema extends SqlContentEntityStorageSchema {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
|
||||
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
|
||||
$field_name = $storage_definition->getName();
|
||||
|
||||
if ($table_name == 'aggregator_item') {
|
||||
switch ($field_name) {
|
||||
case 'timestamp':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
|
||||
break;
|
||||
|
||||
case 'fid':
|
||||
$this->addSharedTableFieldForeignKey($storage_definition, $schema, 'aggregator_feed', 'fid');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
33
web/core/modules/aggregator/src/ItemViewBuilder.php
Normal file
33
web/core/modules/aggregator/src/ItemViewBuilder.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
|
||||
/**
|
||||
* View builder handler for aggregator feed items.
|
||||
*/
|
||||
class ItemViewBuilder extends EntityViewBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildComponents(array &$build, array $entities, array $displays, $view_mode) {
|
||||
parent::buildComponents($build, $entities, $displays, $view_mode);
|
||||
|
||||
foreach ($entities as $id => $entity) {
|
||||
$bundle = $entity->bundle();
|
||||
$display = $displays[$bundle];
|
||||
|
||||
if ($display->getComponent('description')) {
|
||||
$build[$id]['description'] = array(
|
||||
'#markup' => $entity->getDescription(),
|
||||
'#allowed_tags' => _aggregator_allowed_tags(),
|
||||
'#prefix' => '<div class="item-description">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
153
web/core/modules/aggregator/src/ItemsImporter.php
Normal file
153
web/core/modules/aggregator/src/ItemsImporter.php
Normal file
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\aggregator\Plugin\AggregatorPluginManager;
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Defines an importer of aggregator items.
|
||||
*/
|
||||
class ItemsImporter implements ItemsImporterInterface {
|
||||
|
||||
/**
|
||||
* The aggregator fetcher manager.
|
||||
*
|
||||
* @var \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
*/
|
||||
protected $fetcherManager;
|
||||
|
||||
/**
|
||||
* The aggregator processor manager.
|
||||
*
|
||||
* @var \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
*/
|
||||
protected $processorManager;
|
||||
|
||||
/**
|
||||
* The aggregator parser manager.
|
||||
*
|
||||
* @var \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
*/
|
||||
protected $parserManager;
|
||||
|
||||
/**
|
||||
* The aggregator.settings config object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs an Importer object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The factory for configuration objects.
|
||||
* @param \Drupal\aggregator\Plugin\AggregatorPluginManager $fetcher_manager
|
||||
* The aggregator fetcher plugin manager.
|
||||
* @param \Drupal\aggregator\Plugin\AggregatorPluginManager $parser_manager
|
||||
* The aggregator parser plugin manager.
|
||||
* @param \Drupal\aggregator\Plugin\AggregatorPluginManager $processor_manager
|
||||
* The aggregator processor plugin manager.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, AggregatorPluginManager $fetcher_manager, AggregatorPluginManager $parser_manager, AggregatorPluginManager $processor_manager, LoggerInterface $logger) {
|
||||
$this->fetcherManager = $fetcher_manager;
|
||||
$this->processorManager = $processor_manager;
|
||||
$this->parserManager = $parser_manager;
|
||||
$this->config = $config_factory->get('aggregator.settings');
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(FeedInterface $feed) {
|
||||
foreach ($this->processorManager->getDefinitions() as $id => $definition) {
|
||||
$this->processorManager->createInstance($id)->delete($feed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function refresh(FeedInterface $feed) {
|
||||
// Store feed URL to track changes.
|
||||
$feed_url = $feed->getUrl();
|
||||
|
||||
// Fetch the feed.
|
||||
try {
|
||||
$success = $this->fetcherManager->createInstance($this->config->get('fetcher'))->fetch($feed);
|
||||
}
|
||||
catch (PluginException $e) {
|
||||
$success = FALSE;
|
||||
watchdog_exception('aggregator', $e);
|
||||
}
|
||||
|
||||
// Store instances in an array so we dont have to instantiate new objects.
|
||||
$processor_instances = array();
|
||||
foreach ($this->config->get('processors') as $processor) {
|
||||
try {
|
||||
$processor_instances[$processor] = $this->processorManager->createInstance($processor);
|
||||
}
|
||||
catch (PluginException $e) {
|
||||
watchdog_exception('aggregator', $e);
|
||||
}
|
||||
}
|
||||
|
||||
// We store the hash of feed data in the database. When refreshing a
|
||||
// feed we compare stored hash and new hash calculated from downloaded
|
||||
// data. If both are equal we say that feed is not updated.
|
||||
$hash = hash('sha256', $feed->source_string);
|
||||
$has_new_content = $success && ($feed->getHash() != $hash);
|
||||
|
||||
if ($has_new_content) {
|
||||
// Parse the feed.
|
||||
try {
|
||||
if ($this->parserManager->createInstance($this->config->get('parser'))->parse($feed)) {
|
||||
if (!$feed->getWebsiteUrl()) {
|
||||
$feed->setWebsiteUrl($feed->getUrl());
|
||||
}
|
||||
$feed->setHash($hash);
|
||||
// Update feed with parsed data.
|
||||
$feed->save();
|
||||
|
||||
// Log if feed URL has changed.
|
||||
if ($feed->getUrl() != $feed_url) {
|
||||
$this->logger->notice('Updated URL for feed %title to %url.', array('%title' => $feed->label(), '%url' => $feed->getUrl()));
|
||||
}
|
||||
|
||||
$this->logger->notice('There is new syndicated content from %site.', array('%site' => $feed->label()));
|
||||
|
||||
// If there are items on the feed, let enabled processors process them.
|
||||
if (!empty($feed->items)) {
|
||||
foreach ($processor_instances as $instance) {
|
||||
$instance->process($feed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (PluginException $e) {
|
||||
watchdog_exception('aggregator', $e);
|
||||
}
|
||||
}
|
||||
|
||||
// Processing is done, call postProcess on enabled processors.
|
||||
foreach ($processor_instances as $instance) {
|
||||
$instance->postProcess($feed);
|
||||
}
|
||||
|
||||
return $has_new_content;
|
||||
}
|
||||
|
||||
}
|
32
web/core/modules/aggregator/src/ItemsImporterInterface.php
Normal file
32
web/core/modules/aggregator/src/ItemsImporterInterface.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
/**
|
||||
* Provides an interface defining an aggregator items importer.
|
||||
*/
|
||||
interface ItemsImporterInterface {
|
||||
|
||||
/**
|
||||
* Updates the feed items by triggering the import process.
|
||||
*
|
||||
* This process can be slow and lengthy because it relies on network
|
||||
* operations. Calling it on performance critical paths should be avoided.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* The feed which items should be refreshed.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there is new content for the feed FALSE otherwise.
|
||||
*/
|
||||
public function refresh(FeedInterface $feed);
|
||||
|
||||
/**
|
||||
* Deletes all imported items from a feed.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* The feed that associated items should be deleted from.
|
||||
*/
|
||||
public function delete(FeedInterface $feed);
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Manages aggregator plugins.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorParser
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorFetcher
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorProcessor
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see \Drupal\aggregator\Plugin\FetcherInterface
|
||||
* @see \Drupal\aggregator\Plugin\ProcessorInterface
|
||||
* @see \Drupal\aggregator\Plugin\ParserInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
class AggregatorPluginManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a AggregatorPluginManager object.
|
||||
*
|
||||
* @param string $type
|
||||
* The plugin type, for example fetcher.
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
$type_annotations = array(
|
||||
'fetcher' => 'Drupal\aggregator\Annotation\AggregatorFetcher',
|
||||
'parser' => 'Drupal\aggregator\Annotation\AggregatorParser',
|
||||
'processor' => 'Drupal\aggregator\Annotation\AggregatorProcessor',
|
||||
);
|
||||
$plugin_interfaces = array(
|
||||
'fetcher' => 'Drupal\aggregator\Plugin\FetcherInterface',
|
||||
'parser' => 'Drupal\aggregator\Plugin\ParserInterface',
|
||||
'processor' => 'Drupal\aggregator\Plugin\ProcessorInterface',
|
||||
);
|
||||
|
||||
parent::__construct("Plugin/aggregator/$type", $namespaces, $module_handler, $plugin_interfaces[$type], $type_annotations[$type]);
|
||||
$this->setCacheBackend($cache_backend, 'aggregator_' . $type . '_plugins');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\ConfigurablePluginInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
|
||||
/**
|
||||
* Base class for aggregator plugins that implement settings forms.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorParser
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorFetcher
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorProcessor
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see \Drupal\aggregator\Plugin\FetcherInterface
|
||||
* @see \Drupal\aggregator\Plugin\ProcessorInterface
|
||||
* @see \Drupal\aggregator\Plugin\ParserInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class AggregatorPluginSettingsBase extends PluginBase implements PluginFormInterface, ConfigurablePluginInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return array();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Block;
|
||||
|
||||
use Drupal\aggregator\FeedStorageInterface;
|
||||
use Drupal\aggregator\ItemStorageInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides an 'Aggregator feed' block with the latest items from the feed.
|
||||
*
|
||||
* @Block(
|
||||
* id = "aggregator_feed_block",
|
||||
* admin_label = @Translation("Aggregator feed"),
|
||||
* category = @Translation("Lists (Views)")
|
||||
* )
|
||||
*/
|
||||
class AggregatorFeedBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity storage for feeds.
|
||||
*
|
||||
* @var \Drupal\aggregator\FeedStorageInterface
|
||||
*/
|
||||
protected $feedStorage;
|
||||
|
||||
/**
|
||||
* The entity storage for items.
|
||||
*
|
||||
* @var \Drupal\aggregator\ItemStorageInterface
|
||||
*/
|
||||
protected $itemStorage;
|
||||
|
||||
/**
|
||||
* The entity query object for feed items.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryInterface
|
||||
*/
|
||||
protected $itemQuery;
|
||||
|
||||
/**
|
||||
* Constructs an AggregatorFeedBlock object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\aggregator\FeedStorageInterface $feed_storage
|
||||
* The entity storage for feeds.
|
||||
* @param \Drupal\aggregator\ItemStorageInterface $item_storage
|
||||
* The entity storage for feed items.
|
||||
* @param \Drupal\Core\Entity\Query\QueryInterface $item_query
|
||||
* The entity query object for feed items.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, FeedStorageInterface $feed_storage, ItemStorageInterface $item_storage, QueryInterface $item_query) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->feedStorage = $feed_storage;
|
||||
$this->itemStorage = $item_storage;
|
||||
$this->itemQuery = $item_query;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity.manager')->getStorage('aggregator_feed'),
|
||||
$container->get('entity.manager')->getStorage('aggregator_item'),
|
||||
$container->get('entity.query')->get('aggregator_item')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
// By default, the block will contain 10 feed items.
|
||||
return array(
|
||||
'block_count' => 10,
|
||||
'feed' => NULL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockAccess(AccountInterface $account) {
|
||||
// Only grant access to users with the 'access news feeds' permission.
|
||||
return AccessResult::allowedIfHasPermission($account, 'access news feeds');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$feeds = $this->feedStorage->loadMultiple();
|
||||
$options = array();
|
||||
foreach ($feeds as $feed) {
|
||||
$options[$feed->id()] = $feed->label();
|
||||
}
|
||||
$form['feed'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Select the feed that should be displayed'),
|
||||
'#default_value' => $this->configuration['feed'],
|
||||
'#options' => $options,
|
||||
);
|
||||
$range = range(2, 20);
|
||||
$form['block_count'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Number of news items in block'),
|
||||
'#default_value' => $this->configuration['block_count'],
|
||||
'#options' => array_combine($range, $range),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
$this->configuration['block_count'] = $form_state->getValue('block_count');
|
||||
$this->configuration['feed'] = $form_state->getValue('feed');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
// Load the selected feed.
|
||||
if ($feed = $this->feedStorage->load($this->configuration['feed'])) {
|
||||
$result = $this->itemQuery
|
||||
->condition('fid', $feed->id())
|
||||
->range(0, $this->configuration['block_count'])
|
||||
->sort('timestamp', 'DESC')
|
||||
->sort('iid', 'DESC')
|
||||
->execute();
|
||||
|
||||
if ($result) {
|
||||
// Only display the block if there are items to show.
|
||||
$items = $this->itemStorage->loadMultiple($result);
|
||||
|
||||
$build['list'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => [],
|
||||
];
|
||||
foreach ($items as $item) {
|
||||
$build['list']['#items'][$item->id()] = [
|
||||
'#type' => 'link',
|
||||
'#url' => $item->urlInfo(),
|
||||
'#title' => $item->label(),
|
||||
];
|
||||
}
|
||||
$build['more_link'] = [
|
||||
'#type' => 'more_link',
|
||||
'#url' => $feed->urlInfo(),
|
||||
'#attributes' => ['title' => $this->t("View this feed's recent news.")],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
$cache_tags = parent::getCacheTags();
|
||||
$feed = $this->feedStorage->load($this->configuration['feed']);
|
||||
return Cache::mergeTags($cache_tags, $feed->getCacheTags());
|
||||
}
|
||||
|
||||
}
|
37
web/core/modules/aggregator/src/Plugin/FetcherInterface.php
Normal file
37
web/core/modules/aggregator/src/Plugin/FetcherInterface.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator fetcher implementations.
|
||||
*
|
||||
* A fetcher downloads feed data to a Drupal site. The fetcher is called at the
|
||||
* first of the three aggregation stages: first, data is downloaded by the
|
||||
* active fetcher; second, it is converted to a common format by the active
|
||||
* parser; and finally, it is passed to all active processors, which manipulate
|
||||
* or store the data.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorFetcher
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface FetcherInterface {
|
||||
|
||||
/**
|
||||
* Downloads feed data.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* A feed object representing the resource to be downloaded.
|
||||
* $feed->getUrl() 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 bool
|
||||
* TRUE if fetching was successful, FALSE otherwise.
|
||||
*/
|
||||
public function fetch(FeedInterface $feed);
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'aggregator_title' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "aggregator_title",
|
||||
* label = @Translation("Aggregator title"),
|
||||
* description = @Translation("Formats an aggregator item or feed title with an optional link."),
|
||||
* field_types = {
|
||||
* "string"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class AggregatorTitleFormatter extends FormatterBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
$options = parent::defaultSettings();
|
||||
|
||||
$options['display_as_link'] = TRUE;
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::settingsForm($form, $form_state);
|
||||
|
||||
$form['display_as_link'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Link to URL'),
|
||||
'#default_value' => $this->getSetting('display_as_link'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
if ($items->getEntity()->getEntityTypeId() == 'aggregator_feed') {
|
||||
$url_string = $items->getEntity()->getUrl();
|
||||
}
|
||||
else {
|
||||
$url_string = $items->getEntity()->getLink();
|
||||
}
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
if ($this->getSetting('display_as_link') && $url_string) {
|
||||
$elements[$delta] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $item->value,
|
||||
'#url' => Url::fromUri($url_string),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$elements[$delta] = ['#markup' => $item->value];
|
||||
}
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
return (($field_definition->getTargetEntityTypeId() === 'aggregator_item' || $field_definition->getTargetEntityTypeId() === 'aggregator_feed') && $field_definition->getName() === 'title');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'aggregator_xss' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "aggregator_xss",
|
||||
* label = @Translation("Aggregator XSS"),
|
||||
* description = @Translation("Filter output for aggregator items"),
|
||||
* field_types = {
|
||||
* "string",
|
||||
* "string_long",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class AggregatorXSSFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
$elements[$delta] = [
|
||||
'#type' => 'markup',
|
||||
'#markup' => $item->value,
|
||||
'#allowed_tags' => _aggregator_allowed_tags(),
|
||||
];
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
52
web/core/modules/aggregator/src/Plugin/ParserInterface.php
Normal file
52
web/core/modules/aggregator/src/Plugin/ParserInterface.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator parser implementations.
|
||||
*
|
||||
* A parser converts feed item data to a common format. The parser is called
|
||||
* at the second of the three aggregation stages: first, data is downloaded
|
||||
* by the active fetcher; second, it is converted to a common format by the
|
||||
* active parser; and finally, it is passed to all active processors which
|
||||
* manipulate or store the data.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorParser
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface ParserInterface {
|
||||
|
||||
/**
|
||||
* Parses feed data.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* An object describing the resource to be parsed.
|
||||
* $feed->source_string->value contains the raw feed data. Parse the data
|
||||
* and add the following properties to the $feed object:
|
||||
* - description: The human-readable description of the feed.
|
||||
* - link: A full URL that directly relates to the feed.
|
||||
* - image: An image URL used to display an image of the feed.
|
||||
* - etag: An entity tag from the HTTP header used for cache validation to
|
||||
* determine if the content has been changed.
|
||||
* - modified: The UNIX timestamp when the feed was last modified.
|
||||
* - items: An array of feed items. The common format for a single feed item
|
||||
* is an associative array containing:
|
||||
* - title: The human-readable title of the feed item.
|
||||
* - description: The full body text of the item or a summary.
|
||||
* - timestamp: The UNIX timestamp when the feed item was last published.
|
||||
* - author: The author of the feed item.
|
||||
* - guid: The global unique identifier (GUID) string that uniquely
|
||||
* identifies the item. If not available, the link is used to identify
|
||||
* the item.
|
||||
* - link: A full URL to the individual feed item.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if parsing was successful, FALSE otherwise.
|
||||
*/
|
||||
public function parse(FeedInterface $feed);
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator processor implementations.
|
||||
*
|
||||
* A processor acts on parsed feed data. Active processors are called at the
|
||||
* third and last of the aggregation stages: first, data is downloaded by the
|
||||
* active fetcher; second, it is converted to a common format by the active
|
||||
* parser; and finally, it is passed to all active processors that manipulate or
|
||||
* store the data.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorProcessor
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface ProcessorInterface {
|
||||
|
||||
/**
|
||||
* Processes feed data.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* A feed object representing the resource to be processed.
|
||||
* $feed->items contains an array of feed items downloaded and parsed at the
|
||||
* parsing stage. See \Drupal\aggregator\Plugin\FetcherInterface::parse()
|
||||
* for the basic format of a single item in the $feed->items array.
|
||||
* For the exact format refer to the particular parser in use.
|
||||
*/
|
||||
public function process(FeedInterface $feed);
|
||||
|
||||
/**
|
||||
* Refreshes feed information.
|
||||
*
|
||||
* Called after the processing of the feed is completed by all selected
|
||||
* processors.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Object describing feed.
|
||||
*
|
||||
* @see aggregator_refresh()
|
||||
*/
|
||||
public function postProcess(FeedInterface $feed);
|
||||
|
||||
/**
|
||||
* Deletes stored feed data.
|
||||
*
|
||||
* Called by aggregator if either a feed is deleted or a user clicks on
|
||||
* "delete items".
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* The $feed object whose items are being deleted.
|
||||
*/
|
||||
public function delete(FeedInterface $feed);
|
||||
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\QueueWorker;
|
||||
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
use Drupal\Core\Queue\QueueWorkerBase;
|
||||
|
||||
/**
|
||||
* Updates a feed's items.
|
||||
*
|
||||
* @QueueWorker(
|
||||
* id = "aggregator_feeds",
|
||||
* title = @Translation("Aggregator refresh"),
|
||||
* cron = {"time" = 60}
|
||||
* )
|
||||
*/
|
||||
class AggregatorRefresh extends QueueWorkerBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processItem($data) {
|
||||
if ($data instanceof FeedInterface) {
|
||||
$data->refreshItems();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
|
||||
|
||||
/**
|
||||
* Supports validating feed titles.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "FeedTitle",
|
||||
* label = @Translation("Feed title", context = "Validation")
|
||||
* )
|
||||
*/
|
||||
class FeedTitleConstraint extends UniqueFieldConstraint {
|
||||
|
||||
public $message = 'A feed named %value already exists. Enter a unique title.';
|
||||
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldConstraint;
|
||||
|
||||
/**
|
||||
* Supports validating feed URLs.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "FeedUrl",
|
||||
* label = @Translation("Feed URL", context = "Validation")
|
||||
* )
|
||||
*/
|
||||
class FeedUrlConstraint extends UniqueFieldConstraint {
|
||||
|
||||
public $message = 'A feed with this URL %value already exists. Enter a unique URL.';
|
||||
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\aggregator\fetcher;
|
||||
|
||||
use Drupal\aggregator\Plugin\FetcherInterface;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
use Drupal\Component\Datetime\DateTimePlus;
|
||||
use Drupal\Core\Http\ClientFactory;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a default fetcher implementation.
|
||||
*
|
||||
* Uses the http_client service to download the feed.
|
||||
*
|
||||
* @AggregatorFetcher(
|
||||
* id = "aggregator",
|
||||
* title = @Translation("Default fetcher"),
|
||||
* description = @Translation("Downloads data from a URL using Drupal's HTTP request handler.")
|
||||
* )
|
||||
*/
|
||||
class DefaultFetcher implements FetcherInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The HTTP client to fetch the feed data with.
|
||||
*
|
||||
* @var \Drupal\Core\Http\ClientFactory
|
||||
*/
|
||||
protected $httpClientFactory;
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs a DefaultFetcher object.
|
||||
*
|
||||
* @param \Drupal\Core\Http\ClientFactory $http_client_factory
|
||||
* A Guzzle client object.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
*/
|
||||
public function __construct(ClientFactory $http_client_factory, LoggerInterface $logger) {
|
||||
$this->httpClientFactory = $http_client_factory;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$container->get('http_client_factory'),
|
||||
$container->get('logger.factory')->get('aggregator')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fetch(FeedInterface $feed) {
|
||||
$request = new Request('GET', $feed->getUrl());
|
||||
$feed->source_string = FALSE;
|
||||
|
||||
// Generate conditional GET headers.
|
||||
if ($feed->getEtag()) {
|
||||
$request = $request->withAddedHeader('If-None-Match', $feed->getEtag());
|
||||
}
|
||||
if ($feed->getLastModified()) {
|
||||
$request = $request->withAddedHeader('If-Modified-Since', gmdate(DateTimePlus::RFC7231, $feed->getLastModified()));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
/** @var \Psr\Http\Message\UriInterface $actual_uri */
|
||||
$actual_uri = NULL;
|
||||
$response = $this->httpClientFactory->fromOptions(['allow_redirects' => [
|
||||
'on_redirect' => function(RequestInterface $request, ResponseInterface $response, UriInterface $uri) use (&$actual_uri) {
|
||||
$actual_uri = (string) $uri;
|
||||
}
|
||||
]])->send($request);
|
||||
|
||||
// In case of a 304 Not Modified, there is no new content, so return
|
||||
// FALSE.
|
||||
if ($response->getStatusCode() == 304) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$feed->source_string = (string) $response->getBody();
|
||||
if ($response->hasHeader('ETag')) {
|
||||
$feed->setEtag($response->getHeaderLine('ETag'));
|
||||
}
|
||||
if ($response->hasHeader('Last-Modified')) {
|
||||
$feed->setLastModified(strtotime($response->getHeaderLine('Last-Modified')));
|
||||
}
|
||||
$feed->http_headers = $response->getHeaders();
|
||||
|
||||
// Update the feed URL in case of a 301 redirect.
|
||||
if ($actual_uri && $actual_uri !== $feed->getUrl()) {
|
||||
$feed->setUrl($actual_uri);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
catch (RequestException $e) {
|
||||
$this->logger->warning('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->label(), '%error' => $e->getMessage()));
|
||||
drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->label(), '%error' => $e->getMessage())), 'warning');
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\aggregator\parser;
|
||||
|
||||
use Drupal\aggregator\Plugin\ParserInterface;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
use Zend\Feed\Reader\Reader;
|
||||
use Zend\Feed\Reader\Exception\ExceptionInterface;
|
||||
|
||||
/**
|
||||
* Defines a default parser implementation.
|
||||
*
|
||||
* Parses RSS, Atom and RDF feeds.
|
||||
*
|
||||
* @AggregatorParser(
|
||||
* id = "aggregator",
|
||||
* title = @Translation("Default parser"),
|
||||
* description = @Translation("Default parser for RSS, Atom and RDF feeds.")
|
||||
* )
|
||||
*/
|
||||
class DefaultParser implements ParserInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parse(FeedInterface $feed) {
|
||||
// Set our bridge extension manager to Zend Feed.
|
||||
Reader::setExtensionManager(\Drupal::service('feed.bridge.reader'));
|
||||
try {
|
||||
$channel = Reader::importString($feed->source_string);
|
||||
}
|
||||
catch (ExceptionInterface $e) {
|
||||
watchdog_exception('aggregator', $e);
|
||||
drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->label(), '%error' => $e->getMessage())), 'error');
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$feed->setWebsiteUrl($channel->getLink());
|
||||
$feed->setDescription($channel->getDescription());
|
||||
if ($image = $channel->getImage()) {
|
||||
$feed->setImage($image['uri']);
|
||||
}
|
||||
// Initialize items array.
|
||||
$feed->items = array();
|
||||
foreach ($channel as $item) {
|
||||
// Reset the parsed item.
|
||||
$parsed_item = array();
|
||||
// Move the values to an array as expected by processors.
|
||||
$parsed_item['title'] = $item->getTitle();
|
||||
$parsed_item['guid'] = $item->getId();
|
||||
$parsed_item['link'] = $item->getLink();
|
||||
$parsed_item['description'] = $item->getDescription();
|
||||
$parsed_item['author'] = '';
|
||||
if ($author = $item->getAuthor()) {
|
||||
$parsed_item['author'] = $author['name'];
|
||||
}
|
||||
$parsed_item['timestamp'] = '';
|
||||
if ($date = $item->getDateModified()) {
|
||||
$parsed_item['timestamp'] = $date->getTimestamp();
|
||||
}
|
||||
// Store on $feed object. This is where processors will look for parsed items.
|
||||
$feed->items[] = $parsed_item;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\aggregator\processor;
|
||||
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
use Drupal\aggregator\ItemStorageInterface;
|
||||
use Drupal\aggregator\Plugin\AggregatorPluginSettingsBase;
|
||||
use Drupal\aggregator\Plugin\ProcessorInterface;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Form\ConfigFormBaseTrait;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Routing\UrlGeneratorTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a default processor implementation.
|
||||
*
|
||||
* Creates lightweight records from feed items.
|
||||
*
|
||||
* @AggregatorProcessor(
|
||||
* id = "aggregator",
|
||||
* title = @Translation("Default processor"),
|
||||
* description = @Translation("Creates lightweight records from feed items.")
|
||||
* )
|
||||
*/
|
||||
class DefaultProcessor extends AggregatorPluginSettingsBase implements ProcessorInterface, ContainerFactoryPluginInterface {
|
||||
use ConfigFormBaseTrait;
|
||||
use UrlGeneratorTrait;
|
||||
|
||||
/**
|
||||
* Contains the configuration object factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The entity query object for feed items.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryInterface
|
||||
*/
|
||||
protected $itemQuery;
|
||||
|
||||
/**
|
||||
* The entity storage for items.
|
||||
*
|
||||
* @var \Drupal\aggregator\ItemStorageInterface
|
||||
*/
|
||||
protected $itemStorage;
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* Constructs a DefaultProcessor object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
|
||||
* The configuration factory object.
|
||||
* @param \Drupal\Core\Entity\Query\QueryInterface $item_query
|
||||
* The entity query object for feed items.
|
||||
* @param \Drupal\aggregator\ItemStorageInterface $item_storage
|
||||
* The entity storage for feed items.
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config, QueryInterface $item_query, ItemStorageInterface $item_storage, DateFormatterInterface $date_formatter) {
|
||||
$this->configFactory = $config;
|
||||
$this->itemStorage = $item_storage;
|
||||
$this->itemQuery = $item_query;
|
||||
$this->dateFormatter = $date_formatter;
|
||||
// @todo Refactor aggregator plugins to ConfigEntity so merging
|
||||
// the configuration here is not needed.
|
||||
parent::__construct($configuration + $this->getConfiguration(), $plugin_id, $plugin_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('config.factory'),
|
||||
$container->get('entity.query')->get('aggregator_item'),
|
||||
$container->get('entity.manager')->getStorage('aggregator_item'),
|
||||
$container->get('date.formatter')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditableConfigNames() {
|
||||
return ['aggregator.settings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$config = $this->config('aggregator.settings');
|
||||
$processors = $config->get('processors');
|
||||
$info = $this->getPluginDefinition();
|
||||
$counts = array(3, 5, 10, 15, 20, 25);
|
||||
$items = array_map(function ($count) {
|
||||
return $this->formatPlural($count, '1 item', '@count items');
|
||||
}, array_combine($counts, $counts));
|
||||
$intervals = array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800);
|
||||
$period = array_map(array($this->dateFormatter, 'formatInterval'), array_combine($intervals, $intervals));
|
||||
$period[AGGREGATOR_CLEAR_NEVER] = t('Never');
|
||||
|
||||
$form['processors'][$info['id']] = array();
|
||||
// Only wrap into details if there is a basic configuration.
|
||||
if (isset($form['basic_conf'])) {
|
||||
$form['processors'][$info['id']] = array(
|
||||
'#type' => 'details',
|
||||
'#title' => t('Default processor settings'),
|
||||
'#description' => $info['description'],
|
||||
'#open' => in_array($info['id'], $processors),
|
||||
);
|
||||
}
|
||||
|
||||
$form['processors'][$info['id']]['aggregator_summary_items'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Number of items shown in listing pages'),
|
||||
'#default_value' => $config->get('source.list_max'),
|
||||
'#empty_value' => 0,
|
||||
'#options' => $items,
|
||||
);
|
||||
|
||||
$form['processors'][$info['id']]['aggregator_clear'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Discard items older than'),
|
||||
'#default_value' => $config->get('items.expire'),
|
||||
'#options' => $period,
|
||||
'#description' => t('Requires a correctly configured <a href=":cron">cron maintenance task</a>.', array(':cron' => $this->url('system.status'))),
|
||||
);
|
||||
|
||||
$lengths = array(0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000);
|
||||
$options = array_map(function($length) {
|
||||
return ($length == 0) ? t('Unlimited') : $this->formatPlural($length, '1 character', '@count characters');
|
||||
}, array_combine($lengths, $lengths));
|
||||
|
||||
$form['processors'][$info['id']]['aggregator_teaser_length'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Length of trimmed description'),
|
||||
'#default_value' => $config->get('items.teaser_length'),
|
||||
'#options' => $options,
|
||||
'#description' => t('The maximum number of characters used in the trimmed version of content.'),
|
||||
);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['items']['expire'] = $form_state->getValue('aggregator_clear');
|
||||
$this->configuration['items']['teaser_length'] = $form_state->getValue('aggregator_teaser_length');
|
||||
$this->configuration['source']['list_max'] = $form_state->getValue('aggregator_summary_items');
|
||||
// @todo Refactor aggregator plugins to ConfigEntity so this is not needed.
|
||||
$this->setConfiguration($this->configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(FeedInterface $feed) {
|
||||
if (!is_array($feed->items)) {
|
||||
return;
|
||||
}
|
||||
foreach ($feed->items as $item) {
|
||||
// @todo: The default entity view builder 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'])) {
|
||||
$values = array('fid' => $feed->id(), 'guid' => $item['guid']);
|
||||
}
|
||||
elseif ($item['link'] && $item['link'] != $feed->link && $item['link'] != $feed->url) {
|
||||
$values = array('fid' => $feed->id(), 'link' => $item['link']);
|
||||
}
|
||||
else {
|
||||
$values = array('fid' => $feed->id(), 'title' => $item['title']);
|
||||
}
|
||||
|
||||
// Try to load an existing entry.
|
||||
if ($entry = entity_load_multiple_by_properties('aggregator_item', $values)) {
|
||||
$entry = reset($entry);
|
||||
}
|
||||
else {
|
||||
$entry = Item::create(array('langcode' => $feed->language()->getId()));
|
||||
}
|
||||
if ($item['timestamp']) {
|
||||
$entry->setPostedTime($item['timestamp']);
|
||||
}
|
||||
|
||||
// Make sure the item title and author fit in the 255 varchar column.
|
||||
$entry->setTitle(Unicode::truncate($item['title'], 255, TRUE, TRUE));
|
||||
$entry->setAuthor(Unicode::truncate($item['author'], 255, TRUE, TRUE));
|
||||
|
||||
$entry->setFeedId($feed->id());
|
||||
$entry->setLink($item['link']);
|
||||
$entry->setGuid($item['guid']);
|
||||
|
||||
$description = '';
|
||||
if (!empty($item['description'])) {
|
||||
$description = $item['description'];
|
||||
}
|
||||
$entry->setDescription($description);
|
||||
|
||||
$entry->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(FeedInterface $feed) {
|
||||
if ($items = $this->itemStorage->loadByFeed($feed->id())) {
|
||||
$this->itemStorage->delete($items);
|
||||
}
|
||||
// @todo This should be moved out to caller with a different message maybe.
|
||||
drupal_set_message(t('The news items from %site have been deleted.', array('%site' => $feed->label())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements \Drupal\aggregator\Plugin\ProcessorInterface::postProcess().
|
||||
*
|
||||
* Expires items from a feed depending on expiration settings.
|
||||
*/
|
||||
public function postProcess(FeedInterface $feed) {
|
||||
$aggregator_clear = $this->configuration['items']['expire'];
|
||||
|
||||
if ($aggregator_clear != AGGREGATOR_CLEAR_NEVER) {
|
||||
// Delete all items that are older than flush item timer.
|
||||
$age = REQUEST_TIME - $aggregator_clear;
|
||||
$result = $this->itemQuery
|
||||
->condition('fid', $feed->id())
|
||||
->condition('timestamp', $age, '<')
|
||||
->execute();
|
||||
if ($result) {
|
||||
$entities = $this->itemStorage->loadMultiple($result);
|
||||
$this->itemStorage->delete($entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getConfiguration() {
|
||||
return $this->configFactory->get('aggregator.settings')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setConfiguration(array $configuration) {
|
||||
$config = $this->config('aggregator.settings');
|
||||
foreach ($configuration as $key => $value) {
|
||||
$config->set($key, $value);
|
||||
}
|
||||
$config->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal feed source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "aggregator_feed",
|
||||
* source_provider = "aggregator"
|
||||
* )
|
||||
*/
|
||||
class AggregatorFeed extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
return $this->select('aggregator_feed', 'af')
|
||||
->fields('af');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = array(
|
||||
'fid' => $this->t('The feed ID.'),
|
||||
'title' => $this->t('Title of the feed.'),
|
||||
'url' => $this->t('URL to the feed.'),
|
||||
'refresh' => $this->t('Refresh frequency in seconds.'),
|
||||
'checked' => $this->t('Last-checked unix timestamp.'),
|
||||
'link' => $this->t('Parent website of the feed.'),
|
||||
'description' => $this->t("Parent website's description of the feed."),
|
||||
'image' => $this->t('An image representing the feed.'),
|
||||
'etag' => $this->t('Entity tag HTTP response header.'),
|
||||
'modified' => $this->t('When the feed was last modified.'),
|
||||
'block' => $this->t("Number of items to display in the feed's block."),
|
||||
);
|
||||
if ($this->getModuleSchemaVersion('system') >= 7000) {
|
||||
$fields['queued'] = $this->t('Time when this feed was queued for refresh, 0 if not queued.');
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['fid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal aggregator item source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "aggregator_item",
|
||||
* source_provider = "aggregator"
|
||||
* )
|
||||
*/
|
||||
class AggregatorItem extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
return $this->select('aggregator_item', 'ai')
|
||||
->fields('ai')
|
||||
->orderBy('ai.iid');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return array(
|
||||
'iid' => $this->t('Primary Key: Unique ID for feed item.'),
|
||||
'fid' => $this->t('The {aggregator_feed}.fid to which this item belongs.'),
|
||||
'title' => $this->t('Title of the feed item.'),
|
||||
'link' => $this->t('Link to the feed item.'),
|
||||
'author' => $this->t('Author of the feed item.'),
|
||||
'description' => $this->t('Body of the feed item.'),
|
||||
'timestamp' => $this->t('Post date of feed item, as a Unix timestamp.'),
|
||||
'guid' => $this->t('Unique identifier for the feed item.'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['iid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\views\Plugin\views\argument\NumericArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Argument handler to accept an aggregator feed id.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*
|
||||
* @ViewsArgument("aggregator_fid")
|
||||
*/
|
||||
class Fid extends NumericArgument {
|
||||
|
||||
/**
|
||||
* The entity manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Constructs a Drupal\Component\Plugin\PluginBase object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity.manager'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function titleQuery() {
|
||||
$titles = array();
|
||||
|
||||
$feeds = $this->entityManager->getStorage('aggregator_feed')->loadMultiple($this->value);
|
||||
foreach ($feeds as $feed) {
|
||||
$titles[] = $feed->label();
|
||||
}
|
||||
return $titles;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\views\Plugin\views\argument\NumericArgument;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Argument handler to accept an aggregator item id.
|
||||
*
|
||||
* @ingroup views_argument_handlers
|
||||
*
|
||||
* @ViewsArgument("aggregator_iid")
|
||||
*/
|
||||
class Iid extends NumericArgument {
|
||||
|
||||
/**
|
||||
* The entity manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Constructs a Drupal\Component\Plugin\PluginBase object.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin_id for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->entityManager = $entity_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition, $container->get('entity.manager'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function titleQuery() {
|
||||
$titles = array();
|
||||
|
||||
$items = $this->entityManager->getStorage('aggregator_item')->loadMultiple($this->value);
|
||||
foreach ($items as $feed) {
|
||||
$titles[] = $feed->label();
|
||||
}
|
||||
return $titles;
|
||||
}
|
||||
|
||||
}
|
79
web/core/modules/aggregator/src/Plugin/views/row/Rss.php
Normal file
79
web/core/modules/aggregator/src/Plugin/views/row/Rss.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\views\row;
|
||||
|
||||
use Drupal\views\Plugin\views\row\RssPluginBase;
|
||||
|
||||
/**
|
||||
* Defines a row plugin which loads an aggregator item and renders as RSS.
|
||||
*
|
||||
* @ViewsRow(
|
||||
* id = "aggregator_rss",
|
||||
* theme = "views_view_row_rss",
|
||||
* title = @Translation("Aggregator item"),
|
||||
* help = @Translation("Display the aggregator item using the data from the original source."),
|
||||
* base = {"aggregator_item"},
|
||||
* display_types = {"feed"}
|
||||
* )
|
||||
*/
|
||||
class Rss extends RssPluginBase {
|
||||
|
||||
/**
|
||||
* The table the aggregator item is using for storage.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $base_table = 'aggregator_item';
|
||||
|
||||
/**
|
||||
* The actual field which is used to identify a aggregator item.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $base_field = 'iid';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $entityTypeId = 'aggregator_item';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render($row) {
|
||||
$entity = $row->_entity;
|
||||
|
||||
$item = new \stdClass();
|
||||
foreach ($entity as $name => $field) {
|
||||
$item->{$name} = $field->value;
|
||||
}
|
||||
|
||||
$item->elements = array(
|
||||
array(
|
||||
'key' => 'pubDate',
|
||||
// views_view_row_rss takes care about the escaping.
|
||||
'value' => gmdate('r', $entity->timestamp->value),
|
||||
),
|
||||
array(
|
||||
'key' => 'dc:creator',
|
||||
// views_view_row_rss takes care about the escaping.
|
||||
'value' => $entity->author->value,
|
||||
),
|
||||
array(
|
||||
'key' => 'guid',
|
||||
// views_view_row_rss takes care about the escaping.
|
||||
'value' => $entity->guid->value,
|
||||
'attributes' => array('isPermaLink' => 'false'),
|
||||
),
|
||||
);
|
||||
|
||||
$build = array(
|
||||
'#theme' => $this->themeFunctions(),
|
||||
'#view' => $this->view,
|
||||
'#options' => $this->options,
|
||||
'#row' => $item,
|
||||
);
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
95
web/core/modules/aggregator/src/Tests/AddFeedTest.php
Normal file
95
web/core/modules/aggregator/src/Tests/AddFeedTest.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Add feed test.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class AddFeedTest extends AggregatorTestBase {
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and ensures that a feed is unique, checks source, and deletes feed.
|
||||
*/
|
||||
public function testAddFeed() {
|
||||
$feed = $this->createFeed();
|
||||
$feed->refreshItems();
|
||||
|
||||
// Check feed data.
|
||||
$this->assertUrl(\Drupal::url('aggregator.feed_add', [], ['absolute' => TRUE]), [], 'Directed to correct URL.');
|
||||
$this->assertTrue($this->uniqueFeed($feed->label(), $feed->getUrl()), 'The feed is unique.');
|
||||
|
||||
// Check feed source.
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$this->assertResponse(200, 'Feed source exists.');
|
||||
$this->assertText($feed->label(), 'Page title');
|
||||
$this->assertRaw($feed->getWebsiteUrl());
|
||||
|
||||
// Try to add a duplicate.
|
||||
$edit = [
|
||||
'title[0][value]' => $feed->label(),
|
||||
'url[0][value]' => $feed->getUrl(),
|
||||
'refresh' => '900',
|
||||
];
|
||||
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
|
||||
$this->assertRaw(t('A feed named %feed already exists. Enter a unique title.', array('%feed' => $feed->label())));
|
||||
$this->assertRaw(t('A feed with this URL %url already exists. Enter a unique URL.', array('%url' => $feed->getUrl())));
|
||||
|
||||
// Delete feed.
|
||||
$this->deleteFeed($feed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the feed label is escaping when rendering the feed icon.
|
||||
*/
|
||||
public function testFeedLabelEscaping() {
|
||||
$feed = $this->createFeed(NULL, ['title[0][value]' => 'Test feed title <script>alert(123);</script>']);
|
||||
$this->checkForMetaRefresh();
|
||||
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$this->assertResponse(200);
|
||||
|
||||
$this->assertEscaped('Test feed title <script>alert(123);</script>');
|
||||
$this->assertNoRaw('Test feed title <script>alert(123);</script>');
|
||||
|
||||
// Ensure the feed icon title is escaped.
|
||||
$this->assertTrue(strpos(str_replace(["\n", "\r"], '', $this->getRawContent()), 'class="feed-icon"> Subscribe to Test feed title <script>alert(123);</script> feed</a>') !== FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests feeds with very long URLs.
|
||||
*/
|
||||
public function testAddLongFeed() {
|
||||
// Create a feed with a URL of > 255 characters.
|
||||
$long_url = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889&ix=heb";
|
||||
$feed = $this->createFeed($long_url);
|
||||
$feed->refreshItems();
|
||||
|
||||
// Create a second feed of > 255 characters, where the only difference is
|
||||
// after the 255th character.
|
||||
$long_url_2 = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889";
|
||||
$feed_2 = $this->createFeed($long_url_2);
|
||||
$feed->refreshItems();
|
||||
|
||||
// Check feed data.
|
||||
$this->assertTrue($this->uniqueFeed($feed->label(), $feed->getUrl()), 'The first long URL feed is unique.');
|
||||
$this->assertTrue($this->uniqueFeed($feed_2->label(), $feed_2->getUrl()), 'The second long URL feed is unique.');
|
||||
|
||||
// Check feed source.
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$this->assertResponse(200, 'Long URL feed source exists.');
|
||||
$this->assertText($feed->label(), 'Page title');
|
||||
|
||||
// Delete feeds.
|
||||
$this->deleteFeed($feed);
|
||||
$this->deleteFeed($feed_2);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Tests aggregator admin pages.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class AggregatorAdminTest extends AggregatorTestBase {
|
||||
|
||||
/**
|
||||
* Tests the settings form to ensure the correct default values are used.
|
||||
*/
|
||||
public function testSettingsPage() {
|
||||
$this->drupalGet('admin/config');
|
||||
$this->clickLink('Aggregator');
|
||||
$this->clickLink('Settings');
|
||||
// Make sure that test plugins are present.
|
||||
$this->assertText('Test fetcher');
|
||||
$this->assertText('Test parser');
|
||||
$this->assertText('Test processor');
|
||||
|
||||
// Set new values and enable test plugins.
|
||||
$edit = array(
|
||||
'aggregator_allowed_html_tags' => '<a>',
|
||||
'aggregator_summary_items' => 10,
|
||||
'aggregator_clear' => 3600,
|
||||
'aggregator_teaser_length' => 200,
|
||||
'aggregator_fetcher' => 'aggregator_test_fetcher',
|
||||
'aggregator_parser' => 'aggregator_test_parser',
|
||||
'aggregator_processors[aggregator_test_processor]' => 'aggregator_test_processor',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/services/aggregator/settings', $edit, t('Save configuration'));
|
||||
$this->assertText(t('The configuration options have been saved.'));
|
||||
|
||||
foreach ($edit as $name => $value) {
|
||||
$this->assertFieldByName($name, $value, format_string('"@name" has correct default value.', array('@name' => $name)));
|
||||
}
|
||||
|
||||
// Check for our test processor settings form.
|
||||
$this->assertText(t('Dummy length setting'));
|
||||
// Change its value to ensure that settingsSubmit is called.
|
||||
$edit = array(
|
||||
'dummy_length' => 100,
|
||||
);
|
||||
$this->drupalPostForm('admin/config/services/aggregator/settings', $edit, t('Save configuration'));
|
||||
$this->assertText(t('The configuration options have been saved.'));
|
||||
$this->assertFieldByName('dummy_length', 100, '"dummy_length" has correct default value.');
|
||||
|
||||
// Make sure settings form is still accessible even after uninstalling a module
|
||||
// that provides the selected plugins.
|
||||
$this->container->get('module_installer')->uninstall(array('aggregator_test'));
|
||||
$this->resetAll();
|
||||
$this->drupalGet('admin/config/services/aggregator/settings');
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the overview page.
|
||||
*/
|
||||
function testOverviewPage() {
|
||||
$feed = $this->createFeed($this->getRSS091Sample());
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
|
||||
$result = $this->xpath('//table/tbody/tr');
|
||||
// Check if the amount of feeds in the overview matches the amount created.
|
||||
$this->assertEqual(1, count($result), 'Created feed is found in the overview');
|
||||
// Check if the fields in the table match with what's expected.
|
||||
$this->assertEqual($feed->label(), (string) $result[0]->td[0]->a);
|
||||
$count = $this->container->get('entity.manager')->getStorage('aggregator_item')->getItemCount($feed);
|
||||
$this->assertEqual(\Drupal::translation()->formatPlural($count, '1 item', '@count items'), (string) $result[0]->td[1]);
|
||||
|
||||
// Update the items of the first feed.
|
||||
$feed->refreshItems();
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
$result = $this->xpath('//table/tbody/tr');
|
||||
// Check if the fields in the table match with what's expected.
|
||||
$this->assertEqual($feed->label(), (string) $result[0]->td[0]->a);
|
||||
$count = $this->container->get('entity.manager')->getStorage('aggregator_item')->getItemCount($feed);
|
||||
$this->assertEqual(\Drupal::translation()->formatPlural($count, '1 item', '@count items'), (string) $result[0]->td[1]);
|
||||
}
|
||||
|
||||
}
|
45
web/core/modules/aggregator/src/Tests/AggregatorCronTest.php
Normal file
45
web/core/modules/aggregator/src/Tests/AggregatorCronTest.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Update feeds on cron.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class AggregatorCronTest extends AggregatorTestBase {
|
||||
/**
|
||||
* Adds feeds and updates them via cron process.
|
||||
*/
|
||||
public function testCron() {
|
||||
// Create feed and test basic updating on cron.
|
||||
$this->createSampleNodes();
|
||||
$feed = $this->createFeed();
|
||||
$this->cronRun();
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
|
||||
$this->deleteFeedItems($feed);
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
|
||||
$this->cronRun();
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
|
||||
|
||||
// Test feed locking when queued for update.
|
||||
$this->deleteFeedItems($feed);
|
||||
db_update('aggregator_feed')
|
||||
->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->id()))->fetchField());
|
||||
db_update('aggregator_feed')
|
||||
->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->id()))->fetchField());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\views\Entity\View;
|
||||
|
||||
/**
|
||||
* Tests display of aggregator items on the page.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class AggregatorRenderingTest extends AggregatorTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('block', 'test_page_test');
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a feed block to the page and checks its links.
|
||||
*/
|
||||
public function testBlockLinks() {
|
||||
// Create feed.
|
||||
$this->createSampleNodes();
|
||||
$feed = $this->createFeed();
|
||||
$this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
|
||||
|
||||
// Need admin user to be able to access block admin.
|
||||
$admin_user = $this->drupalCreateUser(array(
|
||||
'administer blocks',
|
||||
'access administration pages',
|
||||
'administer news feeds',
|
||||
'access news feeds',
|
||||
));
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$block = $this->drupalPlaceBlock("aggregator_feed_block", array('label' => 'feed-' . $feed->label()));
|
||||
|
||||
// Configure the feed that should be displayed.
|
||||
$block->getPlugin()->setConfigurationValue('feed', $feed->id());
|
||||
$block->getPlugin()->setConfigurationValue('block_count', 2);
|
||||
$block->save();
|
||||
|
||||
// Confirm that the block is now being displayed on pages.
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertText($block->label(), 'Feed block is displayed on the page.');
|
||||
|
||||
// Confirm items appear as links.
|
||||
$items = $this->container->get('entity.manager')->getStorage('aggregator_item')->loadByFeed($feed->id(), 1);
|
||||
$links = $this->xpath('//a[@href = :href]', array(':href' => reset($items)->getLink()));
|
||||
$this->assert(isset($links[0]), 'Item link found.');
|
||||
|
||||
// Find the expected read_more link.
|
||||
$href = $feed->url();
|
||||
$links = $this->xpath('//a[@href = :href]', array(':href' => $href));
|
||||
$this->assert(isset($links[0]), format_string('Link to href %href found.', array('%href' => $href)));
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$cache_tags = explode(' ', $cache_tags_header);
|
||||
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
|
||||
|
||||
// Visit that page.
|
||||
$this->drupalGet($feed->urlInfo()->getInternalPath());
|
||||
$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.');
|
||||
$cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
|
||||
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
|
||||
$this->assertTrue(in_array('aggregator_feed_view', $cache_tags));
|
||||
$this->assertTrue(in_array('aggregator_item_view', $cache_tags));
|
||||
|
||||
// Set the number of news items to 0 to test that the block does not show
|
||||
// up.
|
||||
$block->getPlugin()->setConfigurationValue('block_count', 0);
|
||||
$block->save();
|
||||
// Check that the block is no longer displayed.
|
||||
$this->drupalGet('test-page');
|
||||
$this->assertNoText($block->label(), 'Feed block is not displayed on the page when number of items is set to 0.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a feed and checks that feed's page.
|
||||
*/
|
||||
public function testFeedPage() {
|
||||
// Increase the number of items published in the rss.xml feed so we have
|
||||
// enough articles to test paging.
|
||||
$view = View::load('frontpage');
|
||||
$display = &$view->getDisplay('feed_1');
|
||||
$display['display_options']['pager']['options']['items_per_page'] = 30;
|
||||
$view->save();
|
||||
|
||||
// Create a feed with 30 items.
|
||||
$this->createSampleNodes(30);
|
||||
$feed = $this->createFeed();
|
||||
$this->updateFeedItems($feed, 30);
|
||||
|
||||
// Check for presence of an aggregator pager.
|
||||
$this->drupalGet('aggregator');
|
||||
$elements = $this->xpath("//ul[contains(@class, :class)]", array(':class' => 'pager__items'));
|
||||
$this->assertTrue(!empty($elements), 'Individual source page contains a pager.');
|
||||
|
||||
// Check for sources page title.
|
||||
$this->drupalGet('aggregator/sources');
|
||||
$titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => 'Sources'));
|
||||
$this->assertTrue(!empty($titles), 'Source page contains correct title.');
|
||||
|
||||
// Find the expected read_more link on the sources page.
|
||||
$href = $feed->url();
|
||||
$links = $this->xpath('//a[@href = :href]', array(':href' => $href));
|
||||
$this->assertTrue(isset($links[0]), SafeMarkup::format('Link to href %href found.', array('%href' => $href)));
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$cache_tags = explode(' ', $cache_tags_header);
|
||||
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
|
||||
|
||||
// Check the rss aggregator page as anonymous user.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('aggregator/rss');
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Check the rss aggregator page as admin.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('aggregator/rss');
|
||||
$this->assertResponse(200);
|
||||
$this->assertEqual($this->drupalGetHeader('Content-type'), 'application/rss+xml; charset=utf-8');
|
||||
|
||||
// Check the opml aggregator page.
|
||||
$this->drupalGet('aggregator/opml');
|
||||
$outline = $this->xpath('//outline[1]');
|
||||
$this->assertEqual($outline[0]['type'], 'rss', 'The correct type attribute is used for rss OPML.');
|
||||
$this->assertEqual($outline[0]['text'], $feed->label(), 'The correct text attribute is used for rss OPML.');
|
||||
$this->assertEqual($outline[0]['xmlurl'], $feed->getUrl(), 'The correct xmlUrl attribute is used for rss OPML.');
|
||||
|
||||
// Check for the presence of a pager.
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$elements = $this->xpath("//ul[contains(@class, :class)]", array(':class' => 'pager__items'));
|
||||
$this->assertTrue(!empty($elements), 'Individual source page contains a pager.');
|
||||
$cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
|
||||
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
|
||||
$this->assertTrue(in_array('aggregator_feed_view', $cache_tags));
|
||||
$this->assertTrue(in_array('aggregator_item_view', $cache_tags));
|
||||
}
|
||||
|
||||
}
|
378
web/core/modules/aggregator/src/Tests/AggregatorTestBase.php
Normal file
378
web/core/modules/aggregator/src/Tests/AggregatorTestBase.php
Normal file
|
@ -0,0 +1,378 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines a base class for testing the Aggregator module.
|
||||
*/
|
||||
abstract class AggregatorTestBase extends WebTestBase {
|
||||
|
||||
/**
|
||||
* A user with permission to administer feeds and create content.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['block', 'node', 'aggregator', 'aggregator_test', 'views'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create an Article node type.
|
||||
if ($this->profile != 'standard') {
|
||||
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
|
||||
}
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer news feeds', 'access news feeds', 'create article content'));
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an aggregator feed.
|
||||
*
|
||||
* This method simulates the form submission on path aggregator/sources/add.
|
||||
*
|
||||
* @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 $edit
|
||||
* Array with additional form fields.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* Full feed object if possible.
|
||||
*
|
||||
* @see getFeedEditArray()
|
||||
*/
|
||||
public function createFeed($feed_url = NULL, array $edit = array()) {
|
||||
$edit = $this->getFeedEditArray($feed_url, $edit);
|
||||
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
|
||||
$this->assertText(t('The feed @name has been added.', array('@name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));
|
||||
|
||||
// Verify that the creation message contains a link to a feed.
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title[0][value]'], ':url' => $edit['url[0][value]']))->fetchField();
|
||||
$this->assertTrue(!empty($fid), 'The feed found in database.');
|
||||
return Feed::load($fid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an aggregator feed.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Feed object representing the feed.
|
||||
*/
|
||||
public function deleteFeed(FeedInterface $feed) {
|
||||
$this->drupalPostForm('aggregator/sources/' . $feed->id() . '/delete', array(), t('Delete'));
|
||||
$this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->label())), 'Feed deleted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a randomly generated feed edit array.
|
||||
*
|
||||
* @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 $edit
|
||||
* Array with additional form fields.
|
||||
*
|
||||
* @return array
|
||||
* A feed array.
|
||||
*/
|
||||
public function getFeedEditArray($feed_url = NULL, array $edit = array()) {
|
||||
$feed_name = $this->randomMachineName(10);
|
||||
if (!$feed_url) {
|
||||
$feed_url = \Drupal::url('view.frontpage.feed_1', array(), array(
|
||||
'query' => array('feed' => $feed_name),
|
||||
'absolute' => TRUE,
|
||||
));
|
||||
}
|
||||
$edit += array(
|
||||
'title[0][value]' => $feed_name,
|
||||
'url[0][value]' => $feed_url,
|
||||
'refresh' => '900',
|
||||
);
|
||||
return $edit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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\FeedInterface
|
||||
* A feed object.
|
||||
*/
|
||||
public function getFeedEditObject($feed_url = NULL, array $values = array()) {
|
||||
$feed_name = $this->randomMachineName(10);
|
||||
if (!$feed_url) {
|
||||
$feed_url = \Drupal::url('view.frontpage.feed_1', array(
|
||||
'query' => array('feed' => $feed_name),
|
||||
'absolute' => TRUE,
|
||||
));
|
||||
}
|
||||
$values += array(
|
||||
'title' => $feed_name,
|
||||
'url' => $feed_url,
|
||||
'refresh' => '900',
|
||||
);
|
||||
return Feed::create($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of the randomly created feed array.
|
||||
*
|
||||
* @return int
|
||||
* Number of feed items on default feed created by createFeed().
|
||||
*/
|
||||
public function getDefaultFeedItemCount() {
|
||||
// Our tests are based off of rss.xml, so let's find out how many elements should be related.
|
||||
$feed_count = db_query_range('SELECT COUNT(DISTINCT nid) FROM {node_field_data} n WHERE n.promote = 1 AND n.status = 1', 0, $this->config('system.rss')->get('items.limit'))->fetchField();
|
||||
return $feed_count > 10 ? 10 : $feed_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the feed items.
|
||||
*
|
||||
* This method simulates a click to
|
||||
* admin/config/services/aggregator/update/$fid.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Feed object representing the feed.
|
||||
* @param int|null $expected_count
|
||||
* Expected number of feed items. If omitted no check will happen.
|
||||
*/
|
||||
public function updateFeedItems(FeedInterface $feed, $expected_count = NULL) {
|
||||
// First, let's ensure we can get to the rss xml.
|
||||
$this->drupalGet($feed->getUrl());
|
||||
$this->assertResponse(200, format_string(':url is reachable.', array(':url' => $feed->getUrl())));
|
||||
|
||||
// Attempt to access the update link directly without an access token.
|
||||
$this->drupalGet('admin/config/services/aggregator/update/' . $feed->id());
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Refresh the feed (simulated link click).
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
$this->clickLink('Update items');
|
||||
|
||||
// Ensure we have the right number of items.
|
||||
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()));
|
||||
$feed->items = array();
|
||||
foreach ($result as $item) {
|
||||
$feed->items[] = $item->iid;
|
||||
}
|
||||
|
||||
if ($expected_count !== NULL) {
|
||||
$feed->item_count = count($feed->items);
|
||||
$this->assertEqual($expected_count, $feed->item_count, format_string('Total items in feed equal to the total items in database (@val1 != @val2)', array('@val1' => $expected_count, '@val2' => $feed->item_count)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms an item removal from a feed.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Feed object representing the feed.
|
||||
*/
|
||||
public function deleteFeedItems(FeedInterface $feed) {
|
||||
$this->drupalPostForm('admin/config/services/aggregator/delete/' . $feed->id(), array(), t('Delete items'));
|
||||
$this->assertRaw(t('The news items from %title have been deleted.', array('%title' => $feed->label())), 'Feed items deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds and deletes feed items and ensure that the count is zero.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Feed object representing the feed.
|
||||
* @param int $expected_count
|
||||
* Expected number of feed items.
|
||||
*/
|
||||
public function updateAndDelete(FeedInterface $feed, $expected_count) {
|
||||
$this->updateFeedItems($feed, $expected_count);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
|
||||
$this->assertTrue($count);
|
||||
$this->deleteFeedItems($feed);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
|
||||
$this->assertTrue($count == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the feed name and URL are unique.
|
||||
*
|
||||
* @param string $feed_name
|
||||
* String containing the feed name to check.
|
||||
* @param string $feed_url
|
||||
* String containing the feed url to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if feed is unique.
|
||||
*/
|
||||
public function uniqueFeed($feed_name, $feed_url) {
|
||||
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed_name, ':url' => $feed_url))->fetchField();
|
||||
return (1 == $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid OPML file from an array of feeds.
|
||||
*
|
||||
* @param array $feeds
|
||||
* An array of feeds.
|
||||
*
|
||||
* @return string
|
||||
* Path to valid OPML file.
|
||||
*/
|
||||
public function getValidOpml(array $feeds) {
|
||||
// Properly escape URLs so that XML parsers don't choke on them.
|
||||
foreach ($feeds as &$feed) {
|
||||
$feed['url[0][value]'] = Html::escape($feed['url[0][value]']);
|
||||
}
|
||||
/**
|
||||
* Does not have an XML declaration, must pass the parser.
|
||||
*/
|
||||
$opml = <<<EOF
|
||||
<opml version="1.0">
|
||||
<head></head>
|
||||
<body>
|
||||
<!-- First feed to be imported. -->
|
||||
<outline text="{$feeds[0]['title[0][value]']}" xmlurl="{$feeds[0]['url[0][value]']}" />
|
||||
|
||||
<!-- Second feed. Test string delimitation and attribute order. -->
|
||||
<outline xmlurl='{$feeds[1]['url[0][value]']}' text='{$feeds[1]['title[0][value]']}'/>
|
||||
|
||||
<!-- Test for duplicate URL and title. -->
|
||||
<outline xmlurl="{$feeds[0]['url[0][value]']}" text="Duplicate URL"/>
|
||||
<outline xmlurl="http://duplicate.title" text="{$feeds[1]['title[0][value]']}"/>
|
||||
|
||||
<!-- Test that feeds are only added with required attributes. -->
|
||||
<outline text="{$feeds[2]['title[0][value]']}" />
|
||||
<outline xmlurl="{$feeds[2]['url[0][value]']}" />
|
||||
</body>
|
||||
</opml>
|
||||
EOF;
|
||||
|
||||
$path = 'public://valid-opml.xml';
|
||||
return file_unmanaged_save_data($opml, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid OPML file.
|
||||
*
|
||||
* @return string
|
||||
* Path to invalid OPML file.
|
||||
*/
|
||||
public function getInvalidOpml() {
|
||||
$opml = <<<EOF
|
||||
<opml>
|
||||
<invalid>
|
||||
</opml>
|
||||
EOF;
|
||||
|
||||
$path = 'public://invalid-opml.xml';
|
||||
return file_unmanaged_save_data($opml, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid but empty OPML file.
|
||||
*
|
||||
* @return string
|
||||
* Path to empty OPML file.
|
||||
*/
|
||||
public function getEmptyOpml() {
|
||||
$opml = <<<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<opml version="1.0">
|
||||
<head></head>
|
||||
<body>
|
||||
<outline text="Sample text" />
|
||||
<outline text="Sample text" url="Sample URL" />
|
||||
</body>
|
||||
</opml>
|
||||
EOF;
|
||||
|
||||
$path = 'public://empty-opml.xml';
|
||||
return file_unmanaged_save_data($opml, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a example RSS091 feed.
|
||||
*
|
||||
* @return string
|
||||
* Path to the feed.
|
||||
*/
|
||||
public function getRSS091Sample() {
|
||||
return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_rss091.xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a example Atom feed.
|
||||
*
|
||||
* @return string
|
||||
* Path to the feed.
|
||||
*/
|
||||
public function getAtomSample() {
|
||||
// The content of this sample ATOM feed is based directly off of the
|
||||
// example provided in RFC 4287.
|
||||
return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_atom.xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a example feed.
|
||||
*
|
||||
* @return string
|
||||
* Path to the feed.
|
||||
*/
|
||||
public function getHtmlEntitiesSample() {
|
||||
return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_title_entities.xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates sample article nodes.
|
||||
*
|
||||
* @param int $count
|
||||
* (optional) The number of nodes to generate. Defaults to five.
|
||||
*/
|
||||
public function createSampleNodes($count = 5) {
|
||||
// Post $count article nodes.
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$edit = array();
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit['body[0][value]'] = $this->randomMachineName();
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the plugins coming with aggregator_test module.
|
||||
*/
|
||||
public function enableTestPlugins() {
|
||||
$this->config('aggregator.settings')
|
||||
->set('fetcher', 'aggregator_test_fetcher')
|
||||
->set('parser', 'aggregator_test_parser')
|
||||
->set('processors', array(
|
||||
'aggregator_test_processor' => 'aggregator_test_processor',
|
||||
'aggregator' => 'aggregator',
|
||||
))
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
40
web/core/modules/aggregator/src/Tests/DeleteFeedItemTest.php
Normal file
40
web/core/modules/aggregator/src/Tests/DeleteFeedItemTest.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Delete feed items from a feed.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class DeleteFeedItemTest extends AggregatorTestBase {
|
||||
/**
|
||||
* Tests running "delete items" from 'admin/config/services/aggregator' page.
|
||||
*/
|
||||
public function testDeleteFeedItem() {
|
||||
// Create a bunch of test feeds.
|
||||
$feed_urls = array();
|
||||
// No last-modified, no etag.
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', array(), array('absolute' => TRUE));
|
||||
// Last-modified, but no etag.
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', array('use_last_modified' => 1), array('absolute' => TRUE));
|
||||
// No Last-modified, but etag.
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', array('use_last_modified' => 0, 'use_etag' => 1), array('absolute' => TRUE));
|
||||
// Last-modified and etag.
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', array('use_last_modified' => 1, 'use_etag' => 1), array('absolute' => TRUE));
|
||||
|
||||
foreach ($feed_urls as $feed_url) {
|
||||
$feed = $this->createFeed($feed_url);
|
||||
// Update and delete items two times in a row to make sure that removal
|
||||
// resets all 'modified' information (modified, etag, hash) and allows for
|
||||
// immediate update. There's 8 items in the feed, but one has an empty
|
||||
// title and is skipped.
|
||||
$this->updateAndDelete($feed, 7);
|
||||
$this->updateAndDelete($feed, 7);
|
||||
$this->updateAndDelete($feed, 7);
|
||||
// Delete feed.
|
||||
$this->deleteFeed($feed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
50
web/core/modules/aggregator/src/Tests/DeleteFeedTest.php
Normal file
50
web/core/modules/aggregator/src/Tests/DeleteFeedTest.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Delete feed test.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class DeleteFeedTest extends AggregatorTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('block');
|
||||
|
||||
/**
|
||||
* Deletes a feed and ensures that all of its services are deleted.
|
||||
*/
|
||||
public function testDeleteFeed() {
|
||||
$feed1 = $this->createFeed();
|
||||
$feed2 = $this->createFeed();
|
||||
|
||||
// Place a block for both feeds.
|
||||
$block = $this->drupalPlaceBlock('aggregator_feed_block');
|
||||
$block->getPlugin()->setConfigurationValue('feed', $feed1->id());
|
||||
$block->save();
|
||||
$block2 = $this->drupalPlaceBlock('aggregator_feed_block');
|
||||
$block2->getPlugin()->setConfigurationValue('feed', $feed2->id());
|
||||
$block2->save();
|
||||
|
||||
// Delete feed.
|
||||
$this->deleteFeed($feed1);
|
||||
$this->assertText($feed2->label());
|
||||
$block_storage = $this->container->get('entity.manager')->getStorage('block');
|
||||
$this->assertNull($block_storage->load($block->id()), 'Block for the deleted feed was deleted.');
|
||||
$this->assertEqual($block2->id(), $block_storage->load($block2->id())->id(), 'Block for not deleted feed still exists.');
|
||||
|
||||
// Check feed source.
|
||||
$this->drupalGet('aggregator/sources/' . $feed1->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' => $feed1->label(), ':url' => $feed1->getUrl()))->fetchField();
|
||||
$this->assertFalse($result, 'Feed not found in database');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Tests the display of a feed on the Aggregator list page.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class FeedAdminDisplayTest extends AggregatorTestBase {
|
||||
|
||||
/**
|
||||
* Tests the "Next update" and "Last update" fields.
|
||||
*/
|
||||
public function testFeedUpdateFields() {
|
||||
// Create scheduled feed.
|
||||
$scheduled_feed = $this->createFeed(NULL, array('refresh' => '900'));
|
||||
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
$this->assertResponse(200, 'Aggregator feed overview page exists.');
|
||||
|
||||
// The scheduled feed shows that it has not been updated yet and is
|
||||
// scheduled.
|
||||
$this->assertText('never', 'The scheduled feed has not been updated yet. Last update shows "never".');
|
||||
$this->assertText('imminently', 'The scheduled feed has not been updated yet. Next update shows "imminently".');
|
||||
$this->assertNoText('ago', 'The scheduled feed has not been updated yet. Last update does not show "x x ago".');
|
||||
$this->assertNoText('left', 'The scheduled feed has not been updated yet. Next update does not show "x x left".');
|
||||
|
||||
$this->updateFeedItems($scheduled_feed);
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
|
||||
// After the update, an interval should be displayed on both last updated
|
||||
// and next update.
|
||||
$this->assertNoText('never', 'The scheduled feed has been updated. Last updated changed.');
|
||||
$this->assertNoText('imminently', 'The scheduled feed has been updated. Next update changed.');
|
||||
$this->assertText('ago', 'The scheduled feed been updated. Last update shows "x x ago".');
|
||||
$this->assertText('left', 'The scheduled feed has been updated. Next update shows "x x left".');
|
||||
|
||||
// Delete scheduled feed.
|
||||
$this->deleteFeed($scheduled_feed);
|
||||
|
||||
// Create non-scheduled feed.
|
||||
$non_scheduled_feed = $this->createFeed(NULL, array('refresh' => '0'));
|
||||
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
// The non scheduled feed shows that it has not been updated yet.
|
||||
$this->assertText('never', 'The non scheduled feed has not been updated yet. Last update shows "never".');
|
||||
$this->assertNoText('imminently', 'The non scheduled feed does not show "imminently" as next update.');
|
||||
$this->assertNoText('ago', 'The non scheduled feed has not been updated. It does not show "x x ago" as last update.');
|
||||
$this->assertNoText('left', 'The feed is not scheduled. It does not show a timeframe "x x left" for next update.');
|
||||
|
||||
$this->updateFeedItems($non_scheduled_feed);
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
|
||||
// After the feed update, we still need to see "never" as next update label.
|
||||
// Last update will show an interval.
|
||||
$this->assertNoText('imminently', 'The updated non scheduled feed does not show "imminently" as next update.');
|
||||
$this->assertText('never', 'The updated non scheduled feed still shows "never" as next update.');
|
||||
$this->assertText('ago', 'The non scheduled feed has been updated. It shows "x x ago" as last update.');
|
||||
$this->assertNoText('left', 'The feed is not scheduled. It does not show a timeframe "x x left" for next update.');
|
||||
}
|
||||
|
||||
}
|
52
web/core/modules/aggregator/src/Tests/FeedCacheTagsTest.php
Normal file
52
web/core/modules/aggregator/src/Tests/FeedCacheTagsTest.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests the Feed entity's cache tags.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class FeedCacheTagsTest extends EntityWithUriCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('aggregator');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Give anonymous users permission to access feeds, so that we can verify
|
||||
// the cache tags of cached versions of feeds.
|
||||
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
|
||||
$user_role->grantPermission('access news feeds');
|
||||
$user_role->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
// Create a "Llama" feed.
|
||||
$feed = Feed::create(array(
|
||||
'title' => 'Llama',
|
||||
'url' => 'https://www.drupal.org/',
|
||||
'refresh' => 900,
|
||||
'checked' => 1389919932,
|
||||
'description' => 'Drupal.org',
|
||||
));
|
||||
$feed->save();
|
||||
|
||||
return $feed;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Tests the fetcher plugins functionality and discoverability.
|
||||
*
|
||||
* @group aggregator
|
||||
*
|
||||
* @see \Drupal\aggregator_test\Plugin\aggregator\fetcher\TestFetcher.
|
||||
*/
|
||||
class FeedFetcherPluginTest extends AggregatorTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Enable test plugins.
|
||||
$this->enableTestPlugins();
|
||||
// Create some nodes.
|
||||
$this->createSampleNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fetching functionality.
|
||||
*/
|
||||
public function testfetch() {
|
||||
// Create feed with local url.
|
||||
$feed = $this->createFeed();
|
||||
$this->updateFeedItems($feed);
|
||||
$this->assertFalse(empty($feed->items));
|
||||
|
||||
// Delete items and restore checked property to 0.
|
||||
$this->deleteFeedItems($feed);
|
||||
// Change its name and try again.
|
||||
$feed->setTitle('Do not fetch');
|
||||
$feed->save();
|
||||
$this->updateFeedItems($feed);
|
||||
// Fetch should fail due to feed name.
|
||||
$this->assertTrue(empty($feed->items));
|
||||
}
|
||||
|
||||
}
|
85
web/core/modules/aggregator/src/Tests/FeedLanguageTest.php
Normal file
85
web/core/modules/aggregator/src/Tests/FeedLanguageTest.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests aggregator feeds in multiple languages.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class FeedLanguageTest extends AggregatorTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('language');
|
||||
|
||||
/**
|
||||
* List of langcodes.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $langcodes = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create test languages.
|
||||
$this->langcodes = array(ConfigurableLanguage::load('en'));
|
||||
for ($i = 1; $i < 3; ++$i) {
|
||||
$language = ConfigurableLanguage::create(array(
|
||||
'id' => 'l' . $i,
|
||||
'label' => $this->randomString(),
|
||||
));
|
||||
$language->save();
|
||||
$this->langcodes[$i] = $language->id();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of feeds with a language.
|
||||
*/
|
||||
public function testFeedLanguage() {
|
||||
$admin_user = $this->drupalCreateUser(['administer languages', 'access administration pages', 'administer news feeds', 'access news feeds', 'create article content']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Enable language selection for feeds.
|
||||
$edit['entity_types[aggregator_feed]'] = TRUE;
|
||||
$edit['settings[aggregator_feed][aggregator_feed][settings][language][language_alterable]'] = TRUE;
|
||||
|
||||
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
|
||||
|
||||
/** @var \Drupal\aggregator\FeedInterface[] $feeds */
|
||||
$feeds = array();
|
||||
// Create feeds.
|
||||
$feeds[1] = $this->createFeed(NULL, array('langcode[0][value]' => $this->langcodes[1]));
|
||||
$feeds[2] = $this->createFeed(NULL, array('langcode[0][value]' => $this->langcodes[2]));
|
||||
|
||||
// Make sure that the language has been assigned.
|
||||
$this->assertEqual($feeds[1]->language()->getId(), $this->langcodes[1]);
|
||||
$this->assertEqual($feeds[2]->language()->getId(), $this->langcodes[2]);
|
||||
|
||||
// Create example nodes to create feed items from and then update the feeds.
|
||||
$this->createSampleNodes();
|
||||
$this->cronRun();
|
||||
|
||||
// Loop over the created feed items and verify that their language matches
|
||||
// the one from the feed.
|
||||
foreach ($feeds as $feed) {
|
||||
/** @var \Drupal\aggregator\ItemInterface[] $items */
|
||||
$items = entity_load_multiple_by_properties('aggregator_item', array('fid' => $feed->id()));
|
||||
$this->assertTrue(count($items) > 0, 'Feed items were created.');
|
||||
foreach ($items as $item) {
|
||||
$this->assertEqual($item->language()->getId(), $feed->language()->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
111
web/core/modules/aggregator/src/Tests/FeedParserTest.php
Normal file
111
web/core/modules/aggregator/src/Tests/FeedParserTest.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
|
||||
/**
|
||||
* Tests the built-in feed parser with valid feed samples.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class FeedParserTest extends AggregatorTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Do not delete old aggregator items during these tests, since our sample
|
||||
// feeds have hardcoded dates in them (which may be expired when this test
|
||||
// is run).
|
||||
$this->config('aggregator.settings')->set('items.expire', AGGREGATOR_CLEAR_NEVER)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a feed that uses the RSS 0.91 format.
|
||||
*/
|
||||
public function testRSS091Sample() {
|
||||
$feed = $this->createFeed($this->getRSS091Sample());
|
||||
$feed->refreshItems();
|
||||
$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.');
|
||||
$this->assertRaw('<img src="http://example.com/images/druplicon.png"');
|
||||
|
||||
// Several additional items that include elements over 255 characters.
|
||||
$this->assertRaw("Second example feed item title.");
|
||||
$this->assertText('Long link feed item title');
|
||||
$this->assertText('Long link feed item description');
|
||||
$this->assertLinkByHref('http://example.com/tomorrow/and/tomorrow/and/tomorrow/creeps/in/this/petty/pace/from/day/to/day/to/the/last/syllable/of/recorded/time/and/all/our/yesterdays/have/lighted/fools/the/way/to/dusty/death/out/out/brief/candle/life/is/but/a/walking/shadow/a/poor/player/that/struts/and/frets/his/hour/upon/the/stage/and/is/heard/no/more/it/is/a/tale/told/by/an/idiot/full/of/sound/and/fury/signifying/nothing');
|
||||
$this->assertText('Long author feed item title');
|
||||
$this->assertText('Long author feed item description');
|
||||
$this->assertLinkByHref('http://example.com/long/author');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a feed that uses the Atom format.
|
||||
*/
|
||||
public function testAtomSample() {
|
||||
$feed = $this->createFeed($this->getAtomSample());
|
||||
$feed->refreshItems();
|
||||
$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.');
|
||||
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(':link' => 'http://example.org/2003/12/13/atom03'))->fetchField(), 'Atom entry id element is parsed correctly.');
|
||||
|
||||
// Check for second feed entry.
|
||||
$this->assertText('We tried to stop them, but we failed.');
|
||||
$this->assertLinkByHref('http://example.org/2003/12/14/atom03');
|
||||
$this->assertText('Some other text.');
|
||||
$db_guid = db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(
|
||||
':link' => 'http://example.org/2003/12/14/atom03',
|
||||
))->fetchField();
|
||||
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-bbbb-80da344efa6a', $db_guid, 'Atom entry id element is parsed correctly.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a feed that uses HTML entities in item titles.
|
||||
*/
|
||||
public function testHtmlEntitiesSample() {
|
||||
$feed = $this->createFeed($this->getHtmlEntitiesSample());
|
||||
$feed->refreshItems();
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label())));
|
||||
$this->assertRaw("Quote" Amp&");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a redirected feed is tracked to its target.
|
||||
*/
|
||||
public function testRedirectFeed() {
|
||||
$redirect_url = Url::fromRoute('aggregator_test.redirect')->setAbsolute()->toString();
|
||||
$feed = Feed::create(array('url' => $redirect_url, 'title' => $this->randomMachineName()));
|
||||
$feed->save();
|
||||
$feed->refreshItems();
|
||||
|
||||
// Make sure that the feed URL was updated correctly.
|
||||
$this->assertEqual($feed->getUrl(), \Drupal::url('aggregator_test.feed', array(), array('absolute' => TRUE)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests error handling when an invalid feed is added.
|
||||
*/
|
||||
public function testInvalidFeed() {
|
||||
// Simulate a typo in the URL to force a curl exception.
|
||||
$invalid_url = 'http:/www.drupal.org';
|
||||
$feed = Feed::create(array('url' => $invalid_url, 'title' => $this->randomMachineName()));
|
||||
$feed->save();
|
||||
|
||||
// Update the feed. Use the UI to be able to check the message easily.
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
$this->clickLink(t('Update items'));
|
||||
$this->assertRaw(t('The feed from %title seems to be broken because of error', array('%title' => $feed->label())));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
|
||||
/**
|
||||
* Tests the processor plugins functionality and discoverability.
|
||||
*
|
||||
* @group aggregator
|
||||
*
|
||||
* @see \Drupal\aggregator_test\Plugin\aggregator\processor\TestProcessor.
|
||||
*/
|
||||
class FeedProcessorPluginTest extends AggregatorTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Enable test plugins.
|
||||
$this->enableTestPlugins();
|
||||
// Create some nodes.
|
||||
$this->createSampleNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test processing functionality.
|
||||
*/
|
||||
public function testProcess() {
|
||||
$feed = $this->createFeed();
|
||||
$this->updateFeedItems($feed);
|
||||
foreach ($feed->items as $iid) {
|
||||
$item = Item::load($iid);
|
||||
$this->assertTrue(strpos($item->label(), 'testProcessor') === 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test deleting functionality.
|
||||
*/
|
||||
public function testDelete() {
|
||||
$feed = $this->createFeed();
|
||||
$description = $feed->description->value ?: '';
|
||||
$this->updateAndDelete($feed, NULL);
|
||||
// Make sure the feed title is changed.
|
||||
$entities = entity_load_multiple_by_properties('aggregator_feed', array('description' => $description));
|
||||
$this->assertTrue(empty($entities));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test post-processing functionality.
|
||||
*/
|
||||
public function testPostProcess() {
|
||||
$feed = $this->createFeed(NULL, array('refresh' => 1800));
|
||||
$this->updateFeedItems($feed);
|
||||
$feed_id = $feed->id();
|
||||
// Reset entity cache manually.
|
||||
\Drupal::entityManager()->getStorage('aggregator_feed')->resetCache(array($feed_id));
|
||||
// Reload the feed to get new values.
|
||||
$feed = Feed::load($feed_id);
|
||||
// Make sure its refresh rate doubled.
|
||||
$this->assertEqual($feed->getRefreshRate(), 3600);
|
||||
}
|
||||
|
||||
}
|
124
web/core/modules/aggregator/src/Tests/ImportOpmlTest.php
Normal file
124
web/core/modules/aggregator/src/Tests/ImportOpmlTest.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Tests OPML import.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class ImportOpmlTest extends AggregatorTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('block', 'help');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$admin_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create article content', 'administer blocks'));
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens OPML import form.
|
||||
*/
|
||||
public function openImportForm() {
|
||||
// Enable the help block.
|
||||
$this->drupalPlaceBlock('help_block', array('region' => 'help'));
|
||||
|
||||
$this->drupalGet('admin/config/services/aggregator/add/opml');
|
||||
$this->assertText('A single OPML document may contain many feeds.', 'Found OPML help text.');
|
||||
$this->assertField('files[upload]', 'Found file upload field.');
|
||||
$this->assertField('remote', 'Found Remote URL field.');
|
||||
$this->assertField('refresh', '', 'Found Refresh field.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits form filled with invalid fields.
|
||||
*/
|
||||
public function validateImportFormFields() {
|
||||
$before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
|
||||
$edit = array();
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertRaw(t('<em>Either</em> upload a file or enter a URL.'), 'Error if no fields are filled.');
|
||||
|
||||
$path = $this->getEmptyOpml();
|
||||
$edit = array(
|
||||
'files[upload]' => $path,
|
||||
'remote' => file_create_url($path),
|
||||
);
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertRaw(t('<em>Either</em> upload a file or enter a URL.'), 'Error if both fields are filled.');
|
||||
|
||||
$edit = array('remote' => 'invalidUrl://empty');
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertText(t('The URL invalidUrl://empty is not valid.'), 'Error if the URL is invalid.');
|
||||
|
||||
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
$this->assertEqual($before, $after, 'No feeds were added during the three last form submissions.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits form with invalid, empty, and valid OPML files.
|
||||
*/
|
||||
protected function submitImportForm() {
|
||||
$before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
|
||||
$form['files[upload]'] = $this->getInvalidOpml();
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $form, t('Import'));
|
||||
$this->assertText(t('No new feed has been added.'), 'Attempting to upload invalid XML.');
|
||||
|
||||
$edit = array('remote' => file_create_url($this->getEmptyOpml()));
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertText(t('No new feed has been added.'), 'Attempting to load empty OPML from remote URL.');
|
||||
|
||||
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
$this->assertEqual($before, $after, 'No feeds were added during the two last form submissions.');
|
||||
|
||||
db_delete('aggregator_feed')->execute();
|
||||
|
||||
$feeds[0] = $this->getFeedEditArray();
|
||||
$feeds[1] = $this->getFeedEditArray();
|
||||
$feeds[2] = $this->getFeedEditArray();
|
||||
$edit = array(
|
||||
'files[upload]' => $this->getValidOpml($feeds),
|
||||
'refresh' => '900',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['url[0][value]'])), 'Verifying that a duplicate URL was identified');
|
||||
$this->assertRaw(t('A feed named %title already exists.', array('%title' => $feeds[1]['title[0][value]'])), 'Verifying that a duplicate title was identified');
|
||||
|
||||
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
$this->assertEqual($after, 2, 'Verifying that two distinct feeds were added.');
|
||||
|
||||
$feeds_from_db = db_query("SELECT title, url, refresh FROM {aggregator_feed}");
|
||||
$refresh = TRUE;
|
||||
foreach ($feeds_from_db as $feed) {
|
||||
$title[$feed->url] = $feed->title;
|
||||
$url[$feed->title] = $feed->url;
|
||||
$refresh = $refresh && $feed->refresh == 900;
|
||||
}
|
||||
|
||||
$this->assertEqual($title[$feeds[0]['url[0][value]']], $feeds[0]['title[0][value]'], 'First feed was added correctly.');
|
||||
$this->assertEqual($url[$feeds[1]['title[0][value]']], $feeds[1]['url[0][value]'], 'Second feed was added correctly.');
|
||||
$this->assertTrue($refresh, 'Refresh times are correct.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the import of an OPML file.
|
||||
*/
|
||||
public function testOpmlImport() {
|
||||
$this->openImportForm();
|
||||
$this->validateImportFormFields();
|
||||
$this->submitImportForm();
|
||||
}
|
||||
|
||||
}
|
83
web/core/modules/aggregator/src/Tests/ItemCacheTagsTest.php
Normal file
83
web/core/modules/aggregator/src/Tests/ItemCacheTagsTest.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
|
||||
/**
|
||||
* Tests the Item entity's cache tags.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class ItemCacheTagsTest extends EntityCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('aggregator');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Give anonymous users permission to access feeds, so that we can verify
|
||||
// the cache tags of cached versions of feed items.
|
||||
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
|
||||
$user_role->grantPermission('access news feeds');
|
||||
$user_role->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
// Create a "Camelids" feed.
|
||||
$feed = Feed::create(array(
|
||||
'title' => 'Camelids',
|
||||
'url' => 'https://groups.drupal.org/not_used/167169',
|
||||
'refresh' => 900,
|
||||
'checked' => 1389919932,
|
||||
'description' => 'Drupal Core Group feed',
|
||||
));
|
||||
$feed->save();
|
||||
|
||||
// Create a "Llama" aggregator feed item.
|
||||
$item = Item::create(array(
|
||||
'fid' => $feed->id(),
|
||||
'title' => t('Llama'),
|
||||
'path' => 'https://www.drupal.org/',
|
||||
));
|
||||
$item->save();
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that when creating a feed item, the feed tag is invalidated.
|
||||
*/
|
||||
public function testEntityCreation() {
|
||||
// Create a cache entry that is tagged with a feed cache tag.
|
||||
\Drupal::cache('render')->set('foo', 'bar', CacheBackendInterface::CACHE_PERMANENT, $this->entity->getCacheTags());
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyRenderCache('foo', array('aggregator_feed:1'));
|
||||
|
||||
// Now create a feed item in that feed.
|
||||
Item::create(array(
|
||||
'fid' => $this->entity->getFeedId(),
|
||||
'title' => t('Llama 2'),
|
||||
'path' => 'https://groups.drupal.org/',
|
||||
))->save();
|
||||
|
||||
// Verify a cache miss.
|
||||
$this->assertFalse(\Drupal::cache('render')->get('foo'), 'Creating a new feed item invalidates the cache tag of the feed.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests\Update;
|
||||
|
||||
use Drupal\system\Tests\Update\UpdatePathTestBase;
|
||||
|
||||
/**
|
||||
* Tests that node settings are properly updated during database updates.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class AggregatorUpdateTest extends UpdatePathTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setDatabaseDumpFiles() {
|
||||
$this->databaseDumpFiles = [
|
||||
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the 'Source feed' field is required.
|
||||
*
|
||||
* @see aggregator_update_8200()
|
||||
*/
|
||||
public function testSourceFeedRequired() {
|
||||
// Check that the 'fid' field is not required prior to the update.
|
||||
$field_definition = \Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('fid', 'aggregator_item');
|
||||
$this->assertFalse($field_definition->isRequired());
|
||||
|
||||
// Run updates.
|
||||
$this->runUpdates();
|
||||
|
||||
// Check that the 'fid' field is now required.
|
||||
$field_definition = \Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('fid', 'aggregator_item');
|
||||
$this->assertTrue($field_definition->isRequired());
|
||||
}
|
||||
|
||||
}
|
74
web/core/modules/aggregator/src/Tests/UpdateFeedItemTest.php
Normal file
74
web/core/modules/aggregator/src/Tests/UpdateFeedItemTest.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
|
||||
/**
|
||||
* Update feed items from a feed.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class UpdateFeedItemTest extends AggregatorTestBase {
|
||||
/**
|
||||
* Tests running "update items" from 'admin/config/services/aggregator' page.
|
||||
*/
|
||||
public function testUpdateFeedItem() {
|
||||
$this->createSampleNodes();
|
||||
|
||||
// Create a feed and test updating feed items if possible.
|
||||
$feed = $this->createFeed();
|
||||
if (!empty($feed)) {
|
||||
$this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
|
||||
$this->deleteFeedItems($feed);
|
||||
}
|
||||
|
||||
// Delete feed.
|
||||
$this->deleteFeed($feed);
|
||||
|
||||
// Test updating feed items without valid timestamp information.
|
||||
$edit = array(
|
||||
'title[0][value]' => "Feed without publish timestamp",
|
||||
'url[0][value]' => $this->getRSS091Sample(),
|
||||
);
|
||||
|
||||
$this->drupalGet($edit['url[0][value]']);
|
||||
$this->assertResponse(200);
|
||||
|
||||
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
|
||||
$this->assertText(t('The feed @name has been added.', array('@name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));
|
||||
|
||||
// Verify that the creation message contains a link to a feed.
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url[0][value]']))->fetchField();
|
||||
$feed = Feed::load($fid);
|
||||
|
||||
$feed->refreshItems();
|
||||
$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->id())
|
||||
->fields(array(
|
||||
'checked' => 0,
|
||||
'hash' => '',
|
||||
'etag' => '',
|
||||
'modified' => 0,
|
||||
))
|
||||
->execute();
|
||||
$feed->refreshItems();
|
||||
|
||||
$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)));
|
||||
|
||||
// Make sure updating items works even after uninstalling a module
|
||||
// that provides the selected plugins.
|
||||
$this->enableTestPlugins();
|
||||
$this->container->get('module_installer')->uninstall(array('aggregator_test'));
|
||||
$this->updateFeedItems($feed);
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
|
||||
}
|
50
web/core/modules/aggregator/src/Tests/UpdateFeedTest.php
Normal file
50
web/core/modules/aggregator/src/Tests/UpdateFeedTest.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Update feed test.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class UpdateFeedTest extends AggregatorTestBase {
|
||||
/**
|
||||
* Creates a feed and attempts to update it.
|
||||
*/
|
||||
public function testUpdateFeed() {
|
||||
$remaining_fields = array('title[0][value]', 'url[0][value]', '');
|
||||
foreach ($remaining_fields as $same_field) {
|
||||
$feed = $this->createFeed();
|
||||
|
||||
// Get new feed data array and modify newly created feed.
|
||||
$edit = $this->getFeedEditArray();
|
||||
// Change refresh value.
|
||||
$edit['refresh'] = 1800;
|
||||
if (isset($feed->{$same_field}->value)) {
|
||||
$edit[$same_field] = $feed->{$same_field}->value;
|
||||
}
|
||||
$this->drupalPostForm('aggregator/sources/' . $feed->id() . '/configure', $edit, t('Save'));
|
||||
$this->assertText(t('The feed @name has been updated.', array('@name' => $edit['title[0][value]'])), format_string('The feed %name has been updated.', array('%name' => $edit['title[0][value]'])));
|
||||
|
||||
// Verify that the creation message contains a link to a feed.
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
// Check feed data.
|
||||
$this->assertUrl($feed->url('canonical', ['absolute' => TRUE]));
|
||||
$this->assertTrue($this->uniqueFeed($edit['title[0][value]'], $edit['url[0][value]']), 'The feed is unique.');
|
||||
|
||||
// Check feed source.
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$this->assertResponse(200, 'Feed source exists.');
|
||||
$this->assertText($edit['title[0][value]'], 'Page title');
|
||||
|
||||
// Set correct title so deleteFeed() will work.
|
||||
$feed->title = $edit['title[0][value]'];
|
||||
|
||||
// Delete feed.
|
||||
$this->deleteFeed($feed);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to present an aggregator feed.
|
||||
*
|
||||
* The contents are rendered above feed listings when browsing source feeds.
|
||||
* For example, "example.com/aggregator/sources/1".
|
||||
*
|
||||
* Available variables:
|
||||
* - title: Title of the feed item.
|
||||
* - content: All field items. Use {{ content }} to print them all,
|
||||
* or print a subset such as {{ content.field_example }}. Use
|
||||
* {{ content|without('field_example') }} to temporarily suppress the printing
|
||||
* of a given element.
|
||||
* - title_attributes: Same as attributes, except applied to the main title
|
||||
* tag that appears in the template.
|
||||
* - title_prefix: Additional output populated by modules, intended to be
|
||||
* displayed in front of the main title tag that appears in the template.
|
||||
* - title_suffix: Additional output populated by modules, intended to be
|
||||
* displayed after the main title tag that appears in the template.
|
||||
*
|
||||
* @see template_preprocess_aggregator_feed()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{{ title_prefix }}
|
||||
{% if not full %}
|
||||
<h2{{ title_attributes }}>{{ title }}</h2>
|
||||
{% endif %}
|
||||
{{ title_suffix }}
|
||||
|
||||
{{ content }}
|
|
@ -0,0 +1,31 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to present a feed item in an aggregator page.
|
||||
*
|
||||
* Available variables:
|
||||
* - url: URL to the originating feed item.
|
||||
* - title: Title of the feed item.
|
||||
* - content: All field items. Use {{ content }} to print them all,
|
||||
* or print a subset such as {{ content.field_example }}. Use
|
||||
* {{ content|without('field_example') }} to temporarily suppress the printing
|
||||
* of a given element.
|
||||
* - attributes: HTML attributes for the wrapper.
|
||||
* - title_prefix: Additional output populated by modules, intended to be
|
||||
* displayed in front of the main title tag that appears in the template.
|
||||
* - title_suffix: Additional output populated by modules, intended to be
|
||||
* displayed after the main title tag that appears in the template.
|
||||
*
|
||||
* @see template_preprocess_aggregator_item()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
<article{{ attributes }}>
|
||||
{{ title_prefix }}
|
||||
<h3>
|
||||
<a href="{{ url }}">{{ title }}</a>
|
||||
</h3>
|
||||
{{ title_suffix }}
|
||||
{{ content }}
|
||||
</article>
|
|
@ -0,0 +1,6 @@
|
|||
name: 'Aggregator module tests'
|
||||
type: module
|
||||
description: 'Support module for aggregator related testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
|
@ -0,0 +1,17 @@
|
|||
aggregator_test.feed:
|
||||
path: '/aggregator/test-feed/{use_last_modified}/{use_etag}'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator_test\Controller\AggregatorTestRssController::testFeed'
|
||||
_title: 'Test feed static last modified date'
|
||||
use_last_modified: FALSE
|
||||
use_etag: FALSE
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
aggregator_test.redirect:
|
||||
path: '/aggregator/redirect'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator_test\Controller\AggregatorTestRssController::testRedirect'
|
||||
_title: 'Test feed with a redirect'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
|
||||
<title>Example Feed</title>
|
||||
<link href="http://example.org/" />
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
<author>
|
||||
<name>John Doe</name>
|
||||
</author>
|
||||
<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
|
||||
|
||||
<entry>
|
||||
<title>Atom-Powered Robots Run Amok</title>
|
||||
<link href="http://example.org/2003/12/13/atom03" />
|
||||
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
|
||||
<updated>2003-12-13T18:30:02Z</updated>
|
||||
<summary>Some text.</summary>
|
||||
</entry>
|
||||
|
||||
<entry>
|
||||
<title>We tried to stop them, but we failed.</title>
|
||||
<link href="http://example.org/2003/12/14/atom03" />
|
||||
<id>urn:uuid:1225c695-cfb8-4ebb-bbbb-80da344efa6a</id>
|
||||
<updated>2003-12-14T16:30:02Z</updated>
|
||||
<summary>Some other text.</summary>
|
||||
</entry>
|
||||
|
||||
</feed>
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="0.91">
|
||||
<channel>
|
||||
<title>Example</title>
|
||||
<link>http://example.com</link>
|
||||
<description>Example updates</description>
|
||||
<language>en-us</language>
|
||||
<copyright>Copyright 2000, Example team.</copyright>
|
||||
<managingEditor>editor@example.com</managingEditor>
|
||||
<webMaster>webmaster@example.com</webMaster>
|
||||
<image>
|
||||
<title>Example</title>
|
||||
<url>http://example.com/images/druplicon.png</url>
|
||||
<link>http://example.com</link>
|
||||
<width>88</width>
|
||||
<height>100</height>
|
||||
<description>Example updates</description>
|
||||
</image>
|
||||
<item>
|
||||
<title>First example feed item title</title>
|
||||
<link>http://example.com/example-turns-one</link>
|
||||
<description>First example feed item description.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Second example feed item title. This title is extremely long so that it exceeds the 255 character limit for titles in feed item storage. In fact it's so long that this sentence isn't long enough so I'm rambling a bit to make it longer, nearly there now. Ah now it's long enough so I'll shut up.</title>
|
||||
<link>http://example.com/example-turns-two</link>
|
||||
<description>Second example feed item description.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Long link feed item title.</title>
|
||||
<link>http://example.com/tomorrow/and/tomorrow/and/tomorrow/creeps/in/this/petty/pace/from/day/to/day/to/the/last/syllable/of/recorded/time/and/all/our/yesterdays/have/lighted/fools/the/way/to/dusty/death/out/out/brief/candle/life/is/but/a/walking/shadow/a/poor/player/that/struts/and/frets/his/hour/upon/the/stage/and/is/heard/no/more/it/is/a/tale/told/by/an/idiot/full/of/sound/and/fury/signifying/nothing</link>
|
||||
<description>Long link feed item description.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Long author feed item title.</title>
|
||||
<link>http://example.com/long/author</link>
|
||||
<author>I wanted to get out and walk eastward toward the park through the soft twilight, but each time I tried to go I became entangled in some wild, strident argument which pulled me back, as if with ropes, into my chair. Yet high over the city our line of yellow windows must have contributed their share of human secrecy to the casual watcher in the darkening streets, and I was him too, looking up and wondering. I was within and without, simultaneously enchanted and repelled by the inexhaustible variety of life.</author>
|
||||
<description>Long author feed item description.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title></title>
|
||||
<link>http://example.com/empty/title</link>
|
||||
<description>This is an item with an empty title.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Empty description feed item title.</title>
|
||||
<link>http://example.com/empty/description</link>
|
||||
<description></description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Empty link feed item title.</title>
|
||||
<link></link>
|
||||
<description>This is an item with an empty link.</description>
|
||||
</item>
|
||||
<item>
|
||||
<title>Empty author feed item title.</title>
|
||||
<link>http://example.com/empty/author</link>
|
||||
<author></author>
|
||||
<description>We've tested items with no author, but what about an empty author tag?</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="0.91">
|
||||
<channel>
|
||||
<title>Example with Entities</title>
|
||||
<link>http://example.com</link>
|
||||
<description>Example RSS Feed With HTML Entities in Title</description>
|
||||
<language>en-us</language>
|
||||
<item>
|
||||
<title>Quote" Amp&</title>
|
||||
<link>http://example.com/example-turns-one</link>
|
||||
<description>Some text.</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
|
@ -0,0 +1,2 @@
|
|||
items:
|
||||
dummy_length: 5
|
|
@ -0,0 +1,13 @@
|
|||
# Schema for the configuration files of the Aggregator Test module.
|
||||
|
||||
aggregator_test.settings:
|
||||
type: config_object
|
||||
label: 'Aggregator test settings'
|
||||
mapping:
|
||||
items:
|
||||
type: mapping
|
||||
label: 'Items'
|
||||
mapping:
|
||||
dummy_length:
|
||||
type: integer
|
||||
label: 'Dummy length'
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator_test\Controller;
|
||||
|
||||
use Drupal\Component\Datetime\DateTimePlus;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Controller for the aggregator_test module.
|
||||
*/
|
||||
class AggregatorTestRssController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Generates a test feed and simulates last-modified and etags.
|
||||
*
|
||||
* @param bool $use_last_modified
|
||||
* Set TRUE to send a last modified header.
|
||||
* @param bool $use_etag
|
||||
* Set TRUE to send an etag.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* Information about the current HTTP request.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* A feed that forces cache validation.
|
||||
*/
|
||||
public function testFeed($use_last_modified, $use_etag, Request $request) {
|
||||
$response = new Response();
|
||||
|
||||
$last_modified = strtotime('Sun, 19 Nov 1978 05:00:00 GMT');
|
||||
$etag = Crypt::hashBase64($last_modified);
|
||||
|
||||
$if_modified_since = strtotime($request->server->get('HTTP_IF_MODIFIED_SINCE'));
|
||||
$if_none_match = stripslashes($request->server->get('HTTP_IF_NONE_MATCH'));
|
||||
|
||||
// Send appropriate response. We respond with a 304 not modified on either
|
||||
// etag or on last modified.
|
||||
if ($use_last_modified) {
|
||||
$response->headers->set('Last-Modified', gmdate(DateTimePlus::RFC7231, $last_modified));
|
||||
}
|
||||
if ($use_etag) {
|
||||
$response->headers->set('ETag', $etag);
|
||||
}
|
||||
// Return 304 not modified if either last modified or etag match.
|
||||
if ($last_modified == $if_modified_since || $etag == $if_none_match) {
|
||||
$response->setStatusCode(304);
|
||||
return $response;
|
||||
}
|
||||
|
||||
// The following headers force validation of cache.
|
||||
$response->headers->set('Expires', 'Sun, 19 Nov 1978 05:00:00 GMT');
|
||||
$response->headers->set('Cache-Control', 'must-revalidate');
|
||||
$response->headers->set('Content-Type', 'application/rss+xml; charset=utf-8');
|
||||
|
||||
// Read actual feed from file.
|
||||
$file_name = __DIR__ . '/../../aggregator_test_rss091.xml';
|
||||
$handle = fopen($file_name, 'r');
|
||||
$feed = fread($handle, filesize($file_name));
|
||||
fclose($handle);
|
||||
|
||||
$response->setContent($feed);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a rest redirect to the test feed.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
* A response that redirects users to the test feed.
|
||||
*/
|
||||
public function testRedirect() {
|
||||
return $this->redirect('aggregator_test.feed', [], [], 301);
|
||||
}
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue