Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
56
core/modules/aggregator/src/AggregatorFeedViewsData.php
Normal file
56
core/modules/aggregator/src/AggregatorFeedViewsData.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\AggregatorFeedViewsData.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
50
core/modules/aggregator/src/AggregatorItemViewsData.php
Normal file
50
core/modules/aggregator/src/AggregatorItemViewsData.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\AggregatorItemViewsData.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
53
core/modules/aggregator/src/Annotation/AggregatorFetcher.php
Normal file
53
core/modules/aggregator/src/Annotation/AggregatorFetcher.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Annotation\AggregatorFetcher.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
}
|
53
core/modules/aggregator/src/Annotation/AggregatorParser.php
Normal file
53
core/modules/aggregator/src/Annotation/AggregatorParser.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Annotation\AggregatorParser.
|
||||
*/
|
||||
|
||||
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,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Annotation\AggregatorProcessor.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
}
|
193
core/modules/aggregator/src/Controller/AggregatorController.php
Normal file
193
core/modules/aggregator/src/Controller/AggregatorController.php
Normal file
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Controller\AggregatorController.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Controller;
|
||||
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Datetime\DateFormatter;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Returns responses for aggregator module routes.
|
||||
*/
|
||||
class AggregatorController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatter
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\aggregator\Controller\AggregatorController object.
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatter $date_formatter
|
||||
* The date formatter service.
|
||||
*/
|
||||
public function __construct(DateFormatter $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->formatTimeDiffSince($last_checked))) : $this->t('never'));
|
||||
$row[] = ($last_checked && $refresh_rate ? $this->t('@time left', array('@time' => $this->dateFormatter->formatTimeDiffUntil($last_checked + $refresh_rate))) : $this->t('never'));
|
||||
$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 string
|
||||
* The feed label.
|
||||
*/
|
||||
public function feedTitle(FeedInterface $aggregator_feed) {
|
||||
return Xss::filter($aggregator_feed->label());
|
||||
}
|
||||
|
||||
}
|
403
core/modules/aggregator/src/Entity/Feed.php
Normal file
403
core/modules/aggregator/src/Entity/Feed.php
Normal file
|
@ -0,0 +1,403 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Entity\Feed.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Entity;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Symfony\Component\DependencyInjection\Container;
|
||||
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",
|
||||
* }
|
||||
* },
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Entity\EntityInterface::label().
|
||||
*/
|
||||
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) {
|
||||
$fields['fid'] = BaseFieldDefinition::create('integer')
|
||||
->setLabel(t('Feed ID'))
|
||||
->setDescription(t('The ID of the aggregator feed.'))
|
||||
->setReadOnly(TRUE)
|
||||
->setSetting('unsigned', TRUE);
|
||||
|
||||
$fields['uuid'] = BaseFieldDefinition::create('uuid')
|
||||
->setLabel(t('UUID'))
|
||||
->setDescription(t('The aggregator feed UUID.'))
|
||||
->setReadOnly(TRUE);
|
||||
|
||||
$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['langcode'] = BaseFieldDefinition::create('language')
|
||||
->setLabel(t('Language code'))
|
||||
->setDescription(t('The feed language code.'))
|
||||
->setDisplayOptions('view', array(
|
||||
'type' => 'hidden',
|
||||
))
|
||||
->setDisplayOptions('form', array(
|
||||
'type' => 'language_select',
|
||||
'weight' => 2,
|
||||
));
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
246
core/modules/aggregator/src/Entity/Item.php
Normal file
246
core/modules/aggregator/src/Entity/Item.php
Normal file
|
@ -0,0 +1,246 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Entity\Item.
|
||||
*/
|
||||
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Implements Drupal\Core\Entity\EntityInterface::label().
|
||||
*/
|
||||
public function label() {
|
||||
return $this->get('title')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
$fields['iid'] = BaseFieldDefinition::create('integer')
|
||||
->setLabel(t('Aggregator item ID'))
|
||||
->setDescription(t('The ID of the feed item.'))
|
||||
->setReadOnly(TRUE)
|
||||
->setSetting('unsigned', TRUE);
|
||||
|
||||
$fields['fid'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('Source feed'))
|
||||
->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['langcode'] = BaseFieldDefinition::create('language')
|
||||
->setLabel(t('Language code'))
|
||||
->setDescription(t('The feed item language code.'));
|
||||
|
||||
$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.
|
||||
Cache::invalidateTags($this->getCacheTags());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
return Feed::load($this->getFeedId())->getCacheTags();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Entity URI callback.
|
||||
*/
|
||||
public static function buildUri(ItemInterface $item) {
|
||||
return Url::fromUri($item->getLink());
|
||||
}
|
||||
|
||||
}
|
44
core/modules/aggregator/src/FeedAccessControlHandler.php
Normal file
44
core/modules/aggregator/src/FeedAccessControlHandler.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\FeedAccessControlHandler.
|
||||
*/
|
||||
|
||||
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, $langcode, AccountInterface $account) {
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return AccessResult::allowedIfHasPermission($account, 'access news feeds');
|
||||
break;
|
||||
|
||||
default:
|
||||
return AccessResult::allowedIfHasPermission($account, 'administer news feeds');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
return AccessResult::allowedIfHasPermission($account, 'administer news feeds');
|
||||
}
|
||||
|
||||
}
|
36
core/modules/aggregator/src/FeedForm.php
Normal file
36
core/modules/aggregator/src/FeedForm.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\FeedForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Form controller for the aggregator feed edit forms.
|
||||
*/
|
||||
class FeedForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$feed = $this->entity;
|
||||
$insert = (bool) $feed->id();
|
||||
$feed->save();
|
||||
if ($insert) {
|
||||
drupal_set_message($this->t('The feed %feed has been updated.', array('%feed' => $feed->label())));
|
||||
$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' => $feed->label())));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
244
core/modules/aggregator/src/FeedInterface.php
Normal file
244
core/modules/aggregator/src/FeedInterface.php
Normal file
|
@ -0,0 +1,244 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\FeedInterface.
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
}
|
30
core/modules/aggregator/src/FeedStorage.php
Normal file
30
core/modules/aggregator/src/FeedStorage.php
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\FeedStorage.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
26
core/modules/aggregator/src/FeedStorageInterface.php
Normal file
26
core/modules/aggregator/src/FeedStorageInterface.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\FeedStorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator feed entity storage classes.
|
||||
*/
|
||||
interface FeedStorageInterface extends EntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Returns the fids of feeds that need to be refreshed.
|
||||
*
|
||||
* @return array
|
||||
* A list of feed ids to be refreshed.
|
||||
*/
|
||||
public function getFeedIdsToRefresh();
|
||||
|
||||
}
|
44
core/modules/aggregator/src/FeedStorageSchema.php
Normal file
44
core/modules/aggregator/src/FeedStorageSchema.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\FeedStorageSchema.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
139
core/modules/aggregator/src/FeedViewBuilder.php
Normal file
139
core/modules/aggregator/src/FeedViewBuilder.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\FeedViewBuilder.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Render controller for aggregator feed items.
|
||||
*/
|
||||
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, $langcode = NULL) {
|
||||
parent::buildComponents($build, $entities, $displays, $view_mode, $langcode);
|
||||
|
||||
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, $langcode);
|
||||
|
||||
if ($view_mode == 'full') {
|
||||
// Also add the pager.
|
||||
$build[$id]['pager'] = array('#type' => 'pager');
|
||||
}
|
||||
}
|
||||
|
||||
if ($display->getComponent('description')) {
|
||||
$build[$id]['description'] = array(
|
||||
'#markup' => aggregator_filter_xss($entity->getDescription()),
|
||||
'#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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
41
core/modules/aggregator/src/Form/FeedDeleteForm.php
Normal file
41
core/modules/aggregator/src/Form/FeedDeleteForm.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Form\FeedDeleteForm.
|
||||
*/
|
||||
|
||||
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(),
|
||||
));
|
||||
}
|
||||
|
||||
}
|
49
core/modules/aggregator/src/Form/FeedItemsDeleteForm.php
Normal file
49
core/modules/aggregator/src/Form/FeedItemsDeleteForm.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Form\FeedItemsDeleteForm.
|
||||
*/
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
214
core/modules/aggregator/src/Form/OpmlFeedAdd.php
Normal file
214
core/modules/aggregator/src/Form/OpmlFeedAdd.php
Normal file
|
@ -0,0 +1,214 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Form\OpmlFeedAdd.
|
||||
*/
|
||||
|
||||
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.
|
||||
$file_upload = $this->getRequest()->files->get('files[upload]', NULL, TRUE);
|
||||
if ($form_state->isValueEmpty('remote') == empty($file_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 = $response->getBody(TRUE);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
230
core/modules/aggregator/src/Form/SettingsForm.php
Normal file
230
core/modules/aggregator/src/Form/SettingsForm.php
Normal file
|
@ -0,0 +1,230 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Form\SettingsForm.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
149
core/modules/aggregator/src/ItemInterface.php
Normal file
149
core/modules/aggregator/src/ItemInterface.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\ItemInterface.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
70
core/modules/aggregator/src/ItemStorage.php
Normal file
70
core/modules/aggregator/src/ItemStorage.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\ItemStorage.
|
||||
*/
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
53
core/modules/aggregator/src/ItemStorageInterface.php
Normal file
53
core/modules/aggregator/src/ItemStorageInterface.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\ItemStorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator item entity storage classes.
|
||||
*/
|
||||
interface ItemStorageInterface extends EntityStorageInterface {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
}
|
41
core/modules/aggregator/src/ItemStorageSchema.php
Normal file
41
core/modules/aggregator/src/ItemStorageSchema.php
Normal file
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\ItemStorageSchema.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
37
core/modules/aggregator/src/ItemViewBuilder.php
Normal file
37
core/modules/aggregator/src/ItemViewBuilder.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\ItemViewBuilder.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
|
||||
/**
|
||||
* Render controller for aggregator feed items.
|
||||
*/
|
||||
class ItemViewBuilder extends EntityViewBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $langcode = NULL) {
|
||||
parent::buildComponents($build, $entities, $displays, $view_mode, $langcode);
|
||||
|
||||
foreach ($entities as $id => $entity) {
|
||||
$bundle = $entity->bundle();
|
||||
$display = $displays[$bundle];
|
||||
|
||||
if ($display->getComponent('description')) {
|
||||
$build[$id]['description'] = array(
|
||||
'#markup' => aggregator_filter_xss($entity->getDescription()),
|
||||
'#prefix' => '<div class="item-description">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
158
core/modules/aggregator/src/ItemsImporter.php
Normal file
158
core/modules/aggregator/src/ItemsImporter.php
Normal file
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\ItemsImporter.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
37
core/modules/aggregator/src/ItemsImporterInterface.php
Normal file
37
core/modules/aggregator/src/ItemsImporterInterface.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\ItemsImporterInterface.
|
||||
*/
|
||||
|
||||
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,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\AggregatorPluginManager.
|
||||
*/
|
||||
|
||||
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,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
196
core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php
Normal file
196
core/modules/aggregator/src/Plugin/Block/AggregatorFeedBlock.php
Normal file
|
@ -0,0 +1,196 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\Block\AggregatorFeedBlock.
|
||||
*/
|
||||
|
||||
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();
|
||||
|
||||
$items = $this->itemStorage->loadMultiple($result);
|
||||
|
||||
$more_link = array(
|
||||
'#type' => 'more_link',
|
||||
'#url' => $feed->urlInfo(),
|
||||
'#attributes' => array('title' => $this->t("View this feed's recent news.")),
|
||||
);
|
||||
$read_more = drupal_render($more_link);
|
||||
$rendered_items = array();
|
||||
foreach ($items as $item) {
|
||||
$aggregator_block_item = array(
|
||||
'#type' => 'link',
|
||||
'#url' => $item->urlInfo(),
|
||||
'#title' => $item->label(),
|
||||
);
|
||||
$rendered_items[] = drupal_render($aggregator_block_item);
|
||||
}
|
||||
// Only display the block if there are items to show.
|
||||
if (count($rendered_items) > 0) {
|
||||
$item_list = array(
|
||||
'#theme' => 'item_list',
|
||||
'#items' => $rendered_items,
|
||||
);
|
||||
return array(
|
||||
'#children' => drupal_render($item_list) . $read_more,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
$cache_tags = parent::getCacheTags();
|
||||
$feed = $this->feedStorage->load($this->configuration['feed']);
|
||||
return Cache::mergeTags($cache_tags, $feed->getCacheTags());
|
||||
}
|
||||
|
||||
}
|
42
core/modules/aggregator/src/Plugin/FetcherInterface.php
Normal file
42
core/modules/aggregator/src/Plugin/FetcherInterface.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\FetcherInterface.
|
||||
*/
|
||||
|
||||
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,90 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\Field\FieldFormatter\AggregatorTitleFormatter.
|
||||
*/
|
||||
|
||||
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) {
|
||||
$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,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\Field\FieldFormatter\AggregatorXSSFormatter.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$elements = [];
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
$elements[$delta] = [
|
||||
'#type' => 'markup',
|
||||
'#markup' => aggregator_filter_xss($item->value),
|
||||
];
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
}
|
57
core/modules/aggregator/src/Plugin/ParserInterface.php
Normal file
57
core/modules/aggregator/src/Plugin/ParserInterface.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\ParserInterface.
|
||||
*/
|
||||
|
||||
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);
|
||||
|
||||
}
|
64
core/modules/aggregator/src/Plugin/ProcessorInterface.php
Normal file
64
core/modules/aggregator/src/Plugin/ProcessorInterface.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\ProcessorInterface.
|
||||
*/
|
||||
|
||||
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,33 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\QueueWorker\AggregatorRefresh.
|
||||
*/
|
||||
|
||||
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,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\Validation\Constraint\FeedTitleConstraint.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Supports validating feed titles.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "FeedTitle",
|
||||
* label = @Translation("Feed title", context = "Validation")
|
||||
* )
|
||||
*/
|
||||
class FeedTitleConstraint extends Constraint {
|
||||
|
||||
public $message = 'A feed named %value already exists. Enter a unique title.';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatedBy() {
|
||||
return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\Validation\Constraint\FeedUrlConstraint.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Supports validating feed URLs.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "FeedUrl",
|
||||
* label = @Translation("Feed URL", context = "Validation")
|
||||
* )
|
||||
*/
|
||||
class FeedUrlConstraint extends Constraint {
|
||||
|
||||
public $message = 'A feed with this URL %value already exists. Enter a unique URL.';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validatedBy() {
|
||||
return '\Drupal\Core\Validation\Plugin\Validation\Constraint\UniqueFieldValueValidator';
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\aggregator\fetcher\DefaultFetcher.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Plugin\aggregator\fetcher;
|
||||
|
||||
use Drupal\aggregator\Plugin\FetcherInterface;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
use Drupal\Component\Datetime\DateTimePlus;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
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 \GuzzleHttp\ClientInterface
|
||||
*/
|
||||
protected $httpClient;
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs a DefaultFetcher object.
|
||||
*
|
||||
* @param \GuzzleHttp\ClientInterface $http_client
|
||||
* A Guzzle client object.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
*/
|
||||
public function __construct(ClientInterface $http_client, LoggerInterface $logger) {
|
||||
$this->httpClient = $http_client;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$container->get('http_client'),
|
||||
$container->get('logger.factory')->get('aggregator')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fetch(FeedInterface $feed) {
|
||||
$request = $this->httpClient->createRequest('GET', $feed->getUrl());
|
||||
$feed->source_string = FALSE;
|
||||
|
||||
// Generate conditional GET headers.
|
||||
if ($feed->getEtag()) {
|
||||
$request->addHeader('If-None-Match', $feed->getEtag());
|
||||
}
|
||||
if ($feed->getLastModified()) {
|
||||
$request->addHeader('If-Modified-Since', gmdate(DateTimePlus::RFC7231, $feed->getLastModified()));
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->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();
|
||||
$feed->setEtag($response->getHeader('ETag'));
|
||||
$feed->setLastModified(strtotime($response->getHeader('Last-Modified')));
|
||||
$feed->http_headers = $response->getHeaders();
|
||||
|
||||
// Update the feed URL in case of a 301 redirect.
|
||||
if ($response->getEffectiveUrl() != $feed->getUrl()) {
|
||||
$feed->setUrl($response->getEffectiveUrl());
|
||||
}
|
||||
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,74 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\aggregator\parser\DefaultParser.
|
||||
*/
|
||||
|
||||
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,294 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\aggregator\processor\DefaultProcessor.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Plugin\aggregator\processor;
|
||||
|
||||
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\Database\Database;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Datetime\DateFormatter;
|
||||
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\DateFormatter
|
||||
*/
|
||||
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\DateFormatter $date_formatter
|
||||
* The date formatter service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config, QueryInterface $item_query, ItemStorageInterface $item_storage, DateFormatter $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 = entity_create('aggregator_item', 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();
|
||||
}
|
||||
|
||||
}
|
68
core/modules/aggregator/src/Plugin/views/argument/Fid.php
Normal file
68
core/modules/aggregator/src/Plugin/views/argument/Fid.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\views\argument\Fid.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\views\Plugin\views\argument\NumericArgument;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
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[] = SafeMarkup::checkPlain($feed->label());
|
||||
}
|
||||
return $titles;
|
||||
}
|
||||
|
||||
}
|
68
core/modules/aggregator/src/Plugin/views/argument/Iid.php
Normal file
68
core/modules/aggregator/src/Plugin/views/argument/Iid.php
Normal file
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\views\argument\Iid.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Plugin\views\argument;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\views\Plugin\views\argument\NumericArgument;
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
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[] = SafeMarkup::checkPlain($feed->label());
|
||||
}
|
||||
return $titles;
|
||||
}
|
||||
|
||||
}
|
85
core/modules/aggregator/src/Plugin/views/row/Rss.php
Normal file
85
core/modules/aggregator/src/Plugin/views/row/Rss.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Plugin\views\row\Rss.
|
||||
*/
|
||||
|
||||
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) {
|
||||
// views_view_row_rss takes care about the escaping.
|
||||
$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;
|
||||
}
|
||||
|
||||
}
|
75
core/modules/aggregator/src/Tests/AddFeedTest.php
Normal file
75
core/modules/aggregator/src/Tests/AddFeedTest.php
Normal file
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\AddFeedTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
/**
|
||||
* Add feed test.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class AddFeedTest extends AggregatorTestBase {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
88
core/modules/aggregator/src/Tests/AggregatorAdminTest.php
Normal file
88
core/modules/aggregator/src/Tests/AggregatorAdminTest.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\AggregatorAdminTest.
|
||||
*/
|
||||
|
||||
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('Feed 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]);
|
||||
}
|
||||
}
|
49
core/modules/aggregator/src/Tests/AggregatorCronTest.php
Normal file
49
core/modules/aggregator/src/Tests/AggregatorCronTest.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\AggregatorCronTest.
|
||||
*/
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
148
core/modules/aggregator/src/Tests/AggregatorRenderingTest.php
Normal file
148
core/modules/aggregator/src/Tests/AggregatorRenderingTest.php
Normal file
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\AggregatorRenderingTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* 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');
|
||||
|
||||
/**
|
||||
* 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 = entity_load('view', '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));
|
||||
}
|
||||
|
||||
}
|
376
core/modules/aggregator/src/Tests/AggregatorTestBase.php
Normal file
376
core/modules/aggregator/src/Tests/AggregatorTestBase.php
Normal file
|
@ -0,0 +1,376 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\AggregatorTestBase.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
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 = array('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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->assertRaw(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]'])));
|
||||
|
||||
$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 entity_create('aggregator_feed', $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]'] = htmlspecialchars($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();
|
||||
}
|
||||
}
|
93
core/modules/aggregator/src/Tests/AggregatorTitleTest.php
Normal file
93
core/modules/aggregator/src/Tests/AggregatorTitleTest.php
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\AggregatorTitleTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
|
||||
|
||||
/**
|
||||
* Tests the aggregator_title formatter.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class AggregatorTitleTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('file', 'field', 'options', 'aggregator', 'entity_reference');
|
||||
|
||||
/**
|
||||
* The field name that is tested.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['field']);
|
||||
$this->installEntitySchema('aggregator_feed');
|
||||
$this->installEntitySchema('aggregator_item');
|
||||
|
||||
$this->fieldName = 'title';
|
||||
}
|
||||
|
||||
/*
|
||||
* Tests the formatter output.
|
||||
*/
|
||||
public function testStringFormatter() {
|
||||
// Create an aggregator feed.
|
||||
$aggregator_feed = Feed::create([
|
||||
'title' => 'testing title',
|
||||
'url' => 'http://www.example.com',
|
||||
]);
|
||||
$aggregator_feed->save();
|
||||
|
||||
// Create an aggregator feed item.
|
||||
$aggregator_item = Item::create([
|
||||
'title' => 'test title',
|
||||
'fid' => $aggregator_feed->id(),
|
||||
'link' => 'http://www.example.com',
|
||||
]);
|
||||
$aggregator_item->save();
|
||||
|
||||
// Verify aggregator feed title with and without links.
|
||||
$build = $aggregator_feed->{$this->fieldName}->view(['type' => 'aggregator_title', 'settings' => ['display_as_link' => TRUE]]);
|
||||
$result = $this->render($build);
|
||||
|
||||
$this->assertTrue(strpos($result, 'testing title'));
|
||||
$this->assertTrue(strpos($result, 'href="' . $aggregator_feed->getUrl()) . '"');
|
||||
|
||||
$build = $aggregator_feed->{$this->fieldName}->view(['type' => 'aggregator_title', 'settings' => ['display_as_link' => FALSE]]);
|
||||
$result = $this->render($build);
|
||||
$this->assertTrue(strpos($result, 'testing title') === 0);
|
||||
$this->assertTrue(strpos($result, $aggregator_feed->getUrl()) === FALSE);
|
||||
|
||||
// Verify aggregator item title with and without links.
|
||||
$build = $aggregator_item->{$this->fieldName}->view(['type' => 'aggregator_title', 'settings' => ['display_as_link' =>TRUE]]);
|
||||
$result = $this->render($build);
|
||||
|
||||
$this->assertTrue(strpos($result, 'test title'));
|
||||
$this->assertTrue(strpos($result, 'href="' . $aggregator_item->getLink()) . '"');
|
||||
|
||||
$build = $aggregator_item->{$this->fieldName}->view(['type' => 'aggregator_title', 'settings' => ['display_as_link' => FALSE]]);
|
||||
$result = $this->render($build);
|
||||
$this->assertTrue(strpos($result, 'test title') === 0);
|
||||
$this->assertTrue(strpos($result, $aggregator_item->getLink()) === FALSE);
|
||||
}
|
||||
|
||||
}
|
44
core/modules/aggregator/src/Tests/DeleteFeedItemTest.php
Normal file
44
core/modules/aggregator/src/Tests/DeleteFeedItemTest.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\DeleteFeedItemTest.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
55
core/modules/aggregator/src/Tests/DeleteFeedTest.php
Normal file
55
core/modules/aggregator/src/Tests/DeleteFeedTest.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\DeleteFeedTest.
|
||||
*/
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
}
|
57
core/modules/aggregator/src/Tests/FeedCacheTagsTest.php
Normal file
57
core/modules/aggregator/src/Tests/FeedCacheTagsTest.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\FeedCacheTagsTest.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
48
core/modules/aggregator/src/Tests/FeedFetcherPluginTest.php
Normal file
48
core/modules/aggregator/src/Tests/FeedFetcherPluginTest.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\FeedFetcherPluginTest.
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
89
core/modules/aggregator/src/Tests/FeedLanguageTest.php
Normal file
89
core/modules/aggregator/src/Tests/FeedLanguageTest.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\FeedLanguageTest.
|
||||
*/
|
||||
|
||||
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
core/modules/aggregator/src/Tests/FeedParserTest.php
Normal file
111
core/modules/aggregator/src/Tests/FeedParserTest.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\FeedParserTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Zend\Feed\Reader\Reader;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
// Reset any reader cache between tests.
|
||||
Reader::reset();
|
||||
// Set our bridge extension manager to Zend Feed.
|
||||
$bridge = $this->container->get('feed.bridge.reader');
|
||||
Reader::setExtensionManager($bridge);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.');
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = entity_create('aggregator_feed', 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 = entity_create('aggregator_feed', 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,70 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\FeedProcessorPluginTest.
|
||||
*/
|
||||
|
||||
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();
|
||||
$this->updateAndDelete($feed, NULL);
|
||||
// Make sure the feed title is changed.
|
||||
$entities = entity_load_multiple_by_properties('aggregator_feed', array('description' => $feed->description->value));
|
||||
$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);
|
||||
}
|
||||
}
|
72
core/modules/aggregator/src/Tests/FeedValidationTest.php
Normal file
72
core/modules/aggregator/src/Tests/FeedValidationTest.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\FeedValidationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\system\Tests\Entity\EntityUnitTestBase;
|
||||
|
||||
/**
|
||||
* Tests feed validation constraints.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class FeedValidationTest extends EntityUnitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('aggregator', 'options');
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('aggregator_feed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the feed validation constraints.
|
||||
*/
|
||||
public function testValidation() {
|
||||
// Add feed.
|
||||
$feed = Feed::create([
|
||||
'title' => 'Feed 1',
|
||||
'url' => 'https://www.drupal.org/planet/rss.xml',
|
||||
'refresh' => 900,
|
||||
]);
|
||||
|
||||
$violations = $feed->validate();
|
||||
$this->assertEqual(count($violations), 0);
|
||||
|
||||
$feed->save();
|
||||
|
||||
// Add another feed.
|
||||
/* @var \Drupal\aggregator\FeedInterface $feed */
|
||||
$feed = Feed::create([
|
||||
'title' => 'Feed 1',
|
||||
'url' => 'https://www.drupal.org/planet/rss.xml',
|
||||
'refresh' => 900,
|
||||
]);
|
||||
|
||||
$violations = $feed->validate();
|
||||
|
||||
$this->assertEqual(count($violations), 2);
|
||||
$this->assertEqual($violations[0]->getPropertyPath(), 'title');
|
||||
$this->assertEqual($violations[0]->getMessage(), t('A feed named %value already exists. Enter a unique title.', [
|
||||
'%value' => $feed->label(),
|
||||
]));
|
||||
$this->assertEqual($violations[1]->getPropertyPath(), 'url');
|
||||
$this->assertEqual($violations[1]->getMessage(), t('A feed with this URL %value already exists. Enter a unique URL.', [
|
||||
'%value' => $feed->getUrl(),
|
||||
]));
|
||||
}
|
||||
|
||||
}
|
128
core/modules/aggregator/src/Tests/ImportOpmlTest.php
Normal file
128
core/modules/aggregator/src/Tests/ImportOpmlTest.php
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\ImportOpmlTest.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
87
core/modules/aggregator/src/Tests/ItemCacheTagsTest.php
Normal file
87
core/modules/aggregator/src/Tests/ItemCacheTagsTest.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\ItemCacheTagsTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
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', \Drupal\Core\Cache\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.');
|
||||
}
|
||||
|
||||
}
|
74
core/modules/aggregator/src/Tests/UpdateFeedItemTest.php
Normal file
74
core/modules/aggregator/src/Tests/UpdateFeedItemTest.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\UpdateFeedItemTest.
|
||||
*/
|
||||
|
||||
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(array(200), format_string('URL !url is accessible', array('!url' => $edit['url[0][value]'])));
|
||||
|
||||
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
|
||||
$this->assertRaw(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]'])));
|
||||
|
||||
$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
core/modules/aggregator/src/Tests/UpdateFeedTest.php
Normal file
50
core/modules/aggregator/src/Tests/UpdateFeedTest.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\UpdateFeedTest.
|
||||
*/
|
||||
|
||||
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->assertRaw(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]'])));
|
||||
|
||||
// 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,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\Views\AggregatorFeedViewsFieldAccessTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests\Views;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\views\Tests\Handler\FieldFieldAccessTestBase;
|
||||
|
||||
/**
|
||||
* Tests base field access in Views for the aggregator_feed entity.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class AggregatorFeedViewsFieldAccessTest extends FieldFieldAccessTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['aggregator', 'entity_test', 'options'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE) {
|
||||
parent::setUp($import_test_views);
|
||||
|
||||
$this->installEntitySchema('aggregator_feed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for aggregator_feed fields.
|
||||
*/
|
||||
public function testAggregatorFeedFields() {
|
||||
$feed = Feed::create([
|
||||
'title' => 'Drupal org',
|
||||
'url' => 'https://www.drupal.org/rss.xml',
|
||||
'link' => 'https://www.drupal.org/rss.xml',
|
||||
]);
|
||||
$feed->save();
|
||||
|
||||
// @todo Expand the test coverage in https://www.drupal.org/node/2464635
|
||||
|
||||
// $this->assertFieldAccess('aggregator_feed', 'title', $feed->label());
|
||||
$this->assertFieldAccess('aggregator_feed', 'langcode', $feed->language()->getName());
|
||||
$this->assertFieldAccess('aggregator_feed', 'url', $feed->getUrl());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\Views\AggregatorItemViewsFieldAccessTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests\Views;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
use Drupal\views\Tests\Handler\FieldFieldAccessTestBase;
|
||||
|
||||
/**
|
||||
* Tests base field access in Views for the aggregator_item entity.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class AggregatorItemViewsFieldAccessTest extends FieldFieldAccessTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['aggregator', 'entity_test', 'options'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE) {
|
||||
parent::setUp($import_test_views);
|
||||
|
||||
$this->installEntitySchema('aggregator_feed');
|
||||
$this->installEntitySchema('aggregator_item');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks access for aggregator_item fields.
|
||||
*/
|
||||
public function testAggregatorItemFields() {
|
||||
$feed = Feed::create([
|
||||
'title' => 'Drupal org',
|
||||
'url' => 'https://www.drupal.org/rss.xml',
|
||||
]);
|
||||
$feed->save();
|
||||
$item = Item::create([
|
||||
'title' => 'Test title',
|
||||
'fid' => $feed->id(),
|
||||
'description' => 'Test description',
|
||||
]);
|
||||
|
||||
$item->save();
|
||||
|
||||
// @todo Expand the test coverage in https://www.drupal.org/node/2464635
|
||||
|
||||
$this->assertFieldAccess('aggregator_item', 'title', $item->getTitle());
|
||||
$this->assertFieldAccess('aggregator_item', 'langcode', $item->language()->getName());
|
||||
$this->assertFieldAccess('aggregator_item', 'description', $item->getDescription());
|
||||
}
|
||||
|
||||
}
|
125
core/modules/aggregator/src/Tests/Views/IntegrationTest.php
Normal file
125
core/modules/aggregator/src/Tests/Views/IntegrationTest.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\Views\IntegrationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests\Views;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\views\Views;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Drupal\views\Tests\ViewUnitTestBase;
|
||||
|
||||
/**
|
||||
* Tests basic integration of views data from the aggregator module.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class IntegrationTest extends ViewUnitTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('aggregator', 'aggregator_test_views', 'system', 'field', 'options', 'user');
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = array('test_aggregator_items');
|
||||
|
||||
/**
|
||||
* The entity storage for aggregator items.
|
||||
*
|
||||
* @var \Drupal\aggregator\ItemStorage
|
||||
*/
|
||||
protected $itemStorage;
|
||||
|
||||
/**
|
||||
* The entity storage for aggregator feeds.
|
||||
*
|
||||
* @var \Drupal\aggregator\FeedStorage
|
||||
*/
|
||||
protected $feedStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('aggregator_item');
|
||||
$this->installEntitySchema('aggregator_feed');
|
||||
|
||||
ViewTestData::createTestViews(get_class($this), array('aggregator_test_views'));
|
||||
|
||||
$this->itemStorage = $this->container->get('entity.manager')->getStorage('aggregator_item');
|
||||
$this->feedStorage = $this->container->get('entity.manager')->getStorage('aggregator_feed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic aggregator_item view.
|
||||
*/
|
||||
public function testAggregatorItemView() {
|
||||
$feed = $this->feedStorage->create(array(
|
||||
'title' => $this->randomMachineName(),
|
||||
'url' => 'https://www.drupal.org/',
|
||||
'refresh' => 900,
|
||||
'checked' => 123543535,
|
||||
'description' => $this->randomMachineName(),
|
||||
));
|
||||
$feed->save();
|
||||
|
||||
$items = array();
|
||||
$expected = array();
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$values = array();
|
||||
$values['fid'] = $feed->id();
|
||||
$values['timestamp'] = mt_rand(REQUEST_TIME - 10, REQUEST_TIME + 10);
|
||||
$values['title'] = $this->randomMachineName();
|
||||
$values['description'] = $this->randomMachineName();
|
||||
// Add a image to ensure that the sanitizing can be tested below.
|
||||
$values['author'] = $this->randomMachineName() . '<img src="http://example.com/example.png" \>"';
|
||||
$values['link'] = 'https://www.drupal.org/node/' . mt_rand(1000, 10000);
|
||||
$values['guid'] = $this->randomString();
|
||||
|
||||
$aggregator_item = $this->itemStorage->create($values);
|
||||
$aggregator_item->save();
|
||||
$items[$aggregator_item->id()] = $aggregator_item;
|
||||
|
||||
$values['iid'] = $aggregator_item->id();
|
||||
$expected[] = $values;
|
||||
}
|
||||
|
||||
$view = Views::getView('test_aggregator_items');
|
||||
$this->executeView($view);
|
||||
|
||||
$column_map = array(
|
||||
'iid' => 'iid',
|
||||
'title' => 'title',
|
||||
'aggregator_item_timestamp' => 'timestamp',
|
||||
'description' => 'description',
|
||||
'aggregator_item_author' => 'author',
|
||||
);
|
||||
$this->assertIdenticalResultset($view, $expected, $column_map);
|
||||
|
||||
// Ensure that the rendering of the linked title works as expected.
|
||||
foreach ($view->result as $row) {
|
||||
$iid = $view->field['iid']->getValue($row);
|
||||
$expected_link = \Drupal::l($items[$iid]->getTitle(), Url::fromUri($items[$iid]->getLink(), ['absolute' => TRUE]));
|
||||
$this->assertEqual($view->field['title']->advancedRender($row), $expected_link, 'Ensure the right link is generated');
|
||||
|
||||
$expected_author = aggregator_filter_xss($items[$iid]->getAuthor());
|
||||
$this->assertEqual($view->field['author']->advancedRender($row), $expected_author, 'Ensure the author got filtered');
|
||||
|
||||
$expected_description = aggregator_filter_xss($items[$iid]->getDescription());
|
||||
$this->assertEqual($view->field['description']->advancedRender($row), $expected_description, 'Ensure the author got filtered');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue