Move all files to 2017/
This commit is contained in:
parent
ac7370f67f
commit
2875863330
15717 changed files with 0 additions and 0 deletions
7
2017/web/core/modules/action/action.info.yml
Normal file
7
2017/web/core/modules/action/action.info.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
name: Actions
|
||||
type: module
|
||||
description: 'Perform tasks on specific events triggered within the system.'
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
configure: entity.action.collection
|
5
2017/web/core/modules/action/action.links.menu.yml
Normal file
5
2017/web/core/modules/action/action.links.menu.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
action.admin:
|
||||
title: Actions
|
||||
description: 'Create tasks that the system can execute.'
|
||||
route_name: entity.action.collection
|
||||
parent: system.admin_config_system
|
4
2017/web/core/modules/action/action.links.task.yml
Normal file
4
2017/web/core/modules/action/action.links.task.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
action.admin:
|
||||
route_name: entity.action.collection
|
||||
title: 'Manage actions'
|
||||
base_route: entity.action.collection
|
51
2017/web/core/modules/action/action.module
Normal file
51
2017/web/core/modules/action/action.module
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This is the Actions module for executing stored actions.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function action_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.action':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Actions module provides tasks that can be executed by the site such as unpublishing content, sending email messages, or blocking a user. Other modules can trigger these actions when specific system events happen; for example, when new content is posted or when a user logs in. Modules can also provide additional actions. For more information, see the <a href=":documentation">online documentation for the Actions module</a>.', [':documentation' => 'https://www.drupal.org/documentation/modules/action']) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . t('Using simple actions') . '</dt>';
|
||||
$output .= '<dd>' . t('<em>Simple actions</em> do not require configuration and are listed automatically as available on the <a href=":actions">Actions page</a>.', [':actions' => \Drupal::url('entity.action.collection')]) . '</dd>';
|
||||
$output .= '<dt>' . t('Creating and configuring advanced actions') . '</dt>';
|
||||
$output .= '<dd>' . t('<em>Advanced actions</em> are user-created and have to be configured individually. Create an advanced action on the <a href=":actions">Actions page</a> by selecting an action type from the drop-down list. Then configure your action, for example by specifying the recipient of an automated email message.', [':actions' => \Drupal::url('entity.action.collection')]) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
|
||||
case 'entity.action.collection':
|
||||
$output = '<p>' . t('There are two types of actions: simple and advanced. Simple actions do not require any additional configuration and are listed here automatically. Advanced actions need to be created and configured before they can be used because they have options that need to be specified; for example, sending an email to a specified address or unpublishing content containing certain words. To create an advanced action, select the action from the drop-down list in the advanced action section below and click the <em>Create</em> button.') . '</p>';
|
||||
return $output;
|
||||
|
||||
case 'action.admin_add':
|
||||
case 'entity.action.edit_form':
|
||||
return t('An advanced action offers additional configuration options which may be filled out below. Changing the <em>Label</em> field is recommended in order to better identify the precise action taking place.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_build().
|
||||
*/
|
||||
function action_entity_type_build(array &$entity_types) {
|
||||
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
|
||||
$entity_types['action']
|
||||
->setFormClass('add', 'Drupal\action\ActionAddForm')
|
||||
->setFormClass('edit', 'Drupal\action\ActionEditForm')
|
||||
->setFormClass('delete', 'Drupal\action\Form\ActionDeleteForm')
|
||||
->setListBuilderClass('Drupal\action\ActionListBuilder')
|
||||
->setLinkTemplate('delete-form', '/admin/config/system/actions/configure/{action}/delete')
|
||||
->setLinkTemplate('edit-form', '/admin/config/system/actions/configure/{action}')
|
||||
->setLinkTemplate('collection', '/admin/config/system/actions');
|
||||
}
|
3
2017/web/core/modules/action/action.permissions.yml
Normal file
3
2017/web/core/modules/action/action.permissions.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
administer actions:
|
||||
title: 'Administer actions'
|
||||
restrict access: true
|
31
2017/web/core/modules/action/action.routing.yml
Normal file
31
2017/web/core/modules/action/action.routing.yml
Normal file
|
@ -0,0 +1,31 @@
|
|||
entity.action.collection:
|
||||
path: '/admin/config/system/actions'
|
||||
defaults:
|
||||
_title: 'Actions'
|
||||
_entity_list: 'action'
|
||||
requirements:
|
||||
_permission: 'administer actions'
|
||||
|
||||
action.admin_add:
|
||||
path: '/admin/config/system/actions/add/{action_id}'
|
||||
defaults:
|
||||
_entity_form: 'action.add'
|
||||
_title: 'Add'
|
||||
requirements:
|
||||
_permission: 'administer actions'
|
||||
|
||||
entity.action.edit_form:
|
||||
path: '/admin/config/system/actions/configure/{action}'
|
||||
defaults:
|
||||
_entity_form: 'action.edit'
|
||||
_title: 'Edit'
|
||||
requirements:
|
||||
_permission: 'administer actions'
|
||||
|
||||
entity.action.delete_form:
|
||||
path: '/admin/config/system/actions/configure/{action}/delete'
|
||||
defaults:
|
||||
_entity_form: 'action.delete'
|
||||
_title: 'Delete'
|
||||
requirements:
|
||||
_permission: 'administer actions'
|
20
2017/web/core/modules/action/action.views_execution.inc
Normal file
20
2017/web/core/modules/action/action.views_execution.inc
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides views runtime hooks for action.module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_views_form_substitutions().
|
||||
*/
|
||||
function action_views_form_substitutions() {
|
||||
$select_all = [
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => FALSE,
|
||||
'#attributes' => ['class' => ['action-table-select-all']],
|
||||
];
|
||||
return [
|
||||
'<!--action-bulk-form-select-all-->' => \Drupal::service('renderer')->render($select_all),
|
||||
];
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
recursion_limit: 35
|
40
2017/web/core/modules/action/config/schema/action.schema.yml
Normal file
40
2017/web/core/modules/action/config/schema/action.schema.yml
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Schema for the configuration files of the Action module.
|
||||
|
||||
action.settings:
|
||||
type: config_object
|
||||
label: 'Action settings'
|
||||
mapping:
|
||||
recursion_limit:
|
||||
type: integer
|
||||
label: 'Recursion limit for actions'
|
||||
|
||||
|
||||
action.configuration.action_send_email_action:
|
||||
type: mapping
|
||||
label: 'Send email configuration'
|
||||
mapping:
|
||||
recipient:
|
||||
type: string
|
||||
label: 'Recipient'
|
||||
subject:
|
||||
type: label
|
||||
label: 'Subject'
|
||||
message:
|
||||
type: text
|
||||
label: 'Message'
|
||||
|
||||
action.configuration.action_goto_action:
|
||||
type: mapping
|
||||
label: 'Redirect to URL configuration'
|
||||
mapping:
|
||||
url:
|
||||
type: string
|
||||
label: 'URL'
|
||||
|
||||
action.configuration.action_message_action:
|
||||
type: mapping
|
||||
label: 'Display a message to the user configuration'
|
||||
mapping:
|
||||
message:
|
||||
type: text
|
||||
label: 'Message'
|
16
2017/web/core/modules/action/migrations/action_settings.yml
Normal file
16
2017/web/core/modules/action/migrations/action_settings.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
id: action_settings
|
||||
label: Action configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- actions_max_stack
|
||||
source_module: action
|
||||
process:
|
||||
recursion_limit: actions_max_stack
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: action.settings
|
46
2017/web/core/modules/action/migrations/d6_action.yml
Normal file
46
2017/web/core/modules/action/migrations/d6_action.yml
Normal file
|
@ -0,0 +1,46 @@
|
|||
id: d6_action
|
||||
label: Actions
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: action
|
||||
process:
|
||||
id:
|
||||
-
|
||||
plugin: machine_name
|
||||
source: aid
|
||||
label: description
|
||||
type: type
|
||||
plugin:
|
||||
-
|
||||
plugin: static_map
|
||||
source: callback
|
||||
map:
|
||||
system_goto_action: action_goto_action
|
||||
system_send_email_action: action_send_email_action
|
||||
system_message_action: action_message_action
|
||||
user_block_ip_action: 0
|
||||
imagecache_flush_action: 0
|
||||
imagecache_generate_all_action: 0
|
||||
imagecache_generate_action: 0
|
||||
comment_publish_action: entity:publish_action:comment
|
||||
comment_unpublish_action: entity:unpublish_action:comment
|
||||
comment_save_action: entity:save_action:comment
|
||||
node_publish_action: entity:publish_action:node
|
||||
node_unpublish_action: entity:unpublish_action:node
|
||||
node_save_action: entity:save_action:node
|
||||
bypass: true
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
configuration:
|
||||
-
|
||||
plugin: default_value
|
||||
source: parameters
|
||||
default_value: "a:0:{}"
|
||||
-
|
||||
plugin: callback
|
||||
callable: unserialize
|
||||
destination:
|
||||
plugin: entity:action
|
43
2017/web/core/modules/action/migrations/d7_action.yml
Normal file
43
2017/web/core/modules/action/migrations/d7_action.yml
Normal file
|
@ -0,0 +1,43 @@
|
|||
id: d7_action
|
||||
label: Actions
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: action
|
||||
process:
|
||||
id:
|
||||
-
|
||||
plugin: machine_name
|
||||
source: aid
|
||||
label: label
|
||||
type: type
|
||||
plugin:
|
||||
-
|
||||
plugin: static_map
|
||||
source: callback
|
||||
map:
|
||||
system_goto_action: action_goto_action
|
||||
system_send_email_action: action_send_email_action
|
||||
system_message_action: action_message_action
|
||||
system_block_ip_action: 0
|
||||
comment_publish_action: entity:publish_action:comment
|
||||
comment_unpublish_action: entity:unpublish_action:comment
|
||||
comment_save_action: entity:save_action:comment
|
||||
node_publish_action: entity:publish_action:node
|
||||
node_unpublish_action: entity:unpublish_action:node
|
||||
node_save_action: entity:save_action:node
|
||||
bypass: true
|
||||
-
|
||||
plugin: skip_on_empty
|
||||
method: row
|
||||
configuration:
|
||||
-
|
||||
plugin: default_value
|
||||
source: parameters
|
||||
default_value: "a:0:{}"
|
||||
-
|
||||
plugin: callback
|
||||
callable: unserialize
|
||||
destination:
|
||||
plugin: entity:action
|
31
2017/web/core/modules/action/src/ActionAddForm.php
Normal file
31
2017/web/core/modules/action/src/ActionAddForm.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action;
|
||||
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a form for action add forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ActionAddForm extends ActionFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param string $action_id
|
||||
* The action ID.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $action_id = NULL) {
|
||||
$this->entity->setPlugin($action_id);
|
||||
|
||||
// Derive the label and type from the action definition.
|
||||
$definition = $this->entity->getPluginDefinition();
|
||||
$this->entity->set('label', $definition['label']);
|
||||
$this->entity->set('type', $definition['type']);
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
}
|
12
2017/web/core/modules/action/src/ActionEditForm.php
Normal file
12
2017/web/core/modules/action/src/ActionEditForm.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action;
|
||||
|
||||
/**
|
||||
* Provides a form for action edit forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ActionEditForm extends ActionFormBase {
|
||||
|
||||
}
|
153
2017/web/core/modules/action/src/ActionFormBase.php
Normal file
153
2017/web/core/modules/action/src/ActionFormBase.php
Normal file
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action;
|
||||
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a base form for action forms.
|
||||
*/
|
||||
abstract class ActionFormBase extends EntityForm {
|
||||
|
||||
/**
|
||||
* The action storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The action entity.
|
||||
*
|
||||
* @var \Drupal\system\ActionConfigEntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* Constructs a new action form.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The action storage.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $storage) {
|
||||
$this->storage = $storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity.manager')->getStorage('action')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#default_value' => $this->entity->label(),
|
||||
'#maxlength' => '255',
|
||||
'#description' => $this->t('A unique label for this advanced action. This label will be displayed in the interface of modules that integrate with actions.'),
|
||||
];
|
||||
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $this->entity->id(),
|
||||
'#disabled' => !$this->entity->isNew(),
|
||||
'#maxlength' => 64,
|
||||
'#description' => $this->t('A unique name for this action. It must only contain lowercase letters, numbers and underscores.'),
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
],
|
||||
];
|
||||
$form['plugin'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $this->entity->get('plugin'),
|
||||
];
|
||||
$form['type'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $this->entity->getType(),
|
||||
];
|
||||
|
||||
if ($plugin = $this->getPlugin()) {
|
||||
$form += $plugin->buildConfigurationForm($form, $form_state);
|
||||
}
|
||||
|
||||
return parent::form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the action already exists.
|
||||
*
|
||||
* @param string $id
|
||||
* The action ID.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the action exists, FALSE otherwise.
|
||||
*/
|
||||
public function exists($id) {
|
||||
$action = $this->storage->load($id);
|
||||
return !empty($action);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state) {
|
||||
$actions = parent::actions($form, $form_state);
|
||||
unset($actions['delete']);
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
if ($plugin = $this->getPlugin()) {
|
||||
$plugin->validateConfigurationForm($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitForm($form, $form_state);
|
||||
if ($plugin = $this->getPlugin()) {
|
||||
$plugin->submitConfigurationForm($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$this->entity->save();
|
||||
$this->messenger()->addStatus($this->t('The action has been successfully saved.'));
|
||||
|
||||
$form_state->setRedirect('entity.action.collection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the action plugin while ensuring it implements configuration form.
|
||||
*
|
||||
* @return \Drupal\Core\Action\ActionInterface|\Drupal\Core\Plugin\PluginFormInterface|null
|
||||
* The action plugin, or NULL if it does not implement configuration forms.
|
||||
*/
|
||||
protected function getPlugin() {
|
||||
if ($this->entity->getPlugin() instanceof PluginFormInterface) {
|
||||
return $this->entity->getPlugin();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
120
2017/web/core/modules/action/src/ActionListBuilder.php
Normal file
120
2017/web/core/modules/action/src/ActionListBuilder.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action;
|
||||
|
||||
use Drupal\Core\Action\ActionManager;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of action entities.
|
||||
*
|
||||
* @see \Drupal\system\Entity\Action
|
||||
* @see action_entity_info()
|
||||
*/
|
||||
class ActionListBuilder extends ConfigEntityListBuilder {
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasConfigurableActions = FALSE;
|
||||
|
||||
/**
|
||||
* The action plugin manager.
|
||||
*
|
||||
* @var \Drupal\Core\Action\ActionManager
|
||||
*/
|
||||
protected $actionManager;
|
||||
|
||||
/**
|
||||
* Constructs a new ActionListBuilder object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
|
||||
* The action storage.
|
||||
* @param \Drupal\Core\Action\ActionManager $action_manager
|
||||
* The action plugin manager.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, ActionManager $action_manager) {
|
||||
parent::__construct($entity_type, $storage);
|
||||
|
||||
$this->actionManager = $action_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity.manager')->getStorage($entity_type->id()),
|
||||
$container->get('plugin.manager.action')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load() {
|
||||
$entities = parent::load();
|
||||
foreach ($entities as $entity) {
|
||||
if ($entity->isConfigurable()) {
|
||||
$this->hasConfigurableActions = TRUE;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['type'] = $entity->getType();
|
||||
$row['label'] = $entity->label();
|
||||
if ($this->hasConfigurableActions) {
|
||||
$row += parent::buildRow($entity);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header = [
|
||||
'type' => t('Action type'),
|
||||
'label' => t('Label'),
|
||||
] + parent::buildHeader();
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$operations = $entity->isConfigurable() ? parent::getDefaultOperations($entity) : [];
|
||||
if (isset($operations['edit'])) {
|
||||
$operations['edit']['title'] = t('Configure');
|
||||
}
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build['action_admin_manage_form'] = \Drupal::formBuilder()->getForm('Drupal\action\Form\ActionAdminManageForm');
|
||||
$build['action_header']['#markup'] = '<h3>' . $this->t('Available actions:') . '</h3>';
|
||||
$build['action_table'] = parent::render();
|
||||
if (!$this->hasConfigurableActions) {
|
||||
unset($build['action_table']['table']['#header']['operations']);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Action\ActionManager;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provides a configuration form for configurable actions.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ActionAdminManageForm extends FormBase {
|
||||
|
||||
/**
|
||||
* The action plugin manager.
|
||||
*
|
||||
* @var \Drupal\Core\Action\ActionManager
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* Constructs a new ActionAdminManageForm.
|
||||
*
|
||||
* @param \Drupal\Core\Action\ActionManager $manager
|
||||
* The action plugin manager.
|
||||
*/
|
||||
public function __construct(ActionManager $manager) {
|
||||
$this->manager = $manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('plugin.manager.action')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'action_admin_manage';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$actions = [];
|
||||
foreach ($this->manager->getDefinitions() as $id => $definition) {
|
||||
if (is_subclass_of($definition['class'], '\Drupal\Core\Plugin\PluginFormInterface')) {
|
||||
$actions[$id] = $definition['label'];
|
||||
}
|
||||
}
|
||||
asort($actions);
|
||||
$form['parent'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Create an advanced action'),
|
||||
'#attributes' => ['class' => ['container-inline']],
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['parent']['action'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Action'),
|
||||
'#title_display' => 'invisible',
|
||||
'#options' => $actions,
|
||||
'#empty_option' => $this->t('- Select -'),
|
||||
];
|
||||
$form['parent']['actions'] = [
|
||||
'#type' => 'actions',
|
||||
];
|
||||
$form['parent']['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Create'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
if ($form_state->getValue('action')) {
|
||||
$form_state->setRedirect(
|
||||
'action.admin_add',
|
||||
['action_id' => $form_state->getValue('action')]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
22
2017/web/core/modules/action/src/Form/ActionDeleteForm.php
Normal file
22
2017/web/core/modules/action/src/Form/ActionDeleteForm.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityDeleteForm;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Builds a form to delete an action.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class ActionDeleteForm extends EntityDeleteForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCancelUrl() {
|
||||
return new Url('entity.action.collection');
|
||||
}
|
||||
|
||||
}
|
216
2017/web/core/modules/action/src/Plugin/Action/EmailAction.php
Normal file
216
2017/web/core/modules/action/src/Plugin/Action/EmailAction.php
Normal file
|
@ -0,0 +1,216 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action\Plugin\Action;
|
||||
|
||||
use Drupal\Component\Render\PlainTextOutput;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\ConfigurableActionBase;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Mail\MailManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Egulias\EmailValidator\EmailValidator;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Sends an email message.
|
||||
*
|
||||
* @Action(
|
||||
* id = "action_send_email_action",
|
||||
* label = @Translation("Send email"),
|
||||
* type = "system"
|
||||
* )
|
||||
*/
|
||||
class EmailAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The token service.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* The user storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* The mail manager
|
||||
*
|
||||
* @var \Drupal\Core\Mail\MailManagerInterface
|
||||
*/
|
||||
protected $mailManager;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The email validator.
|
||||
*
|
||||
* @var \Egulias\EmailValidator\EmailValidator
|
||||
*/
|
||||
protected $emailValidator;
|
||||
|
||||
/**
|
||||
* Constructs a EmailAction 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\Utility\Token $token
|
||||
* The token service.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
* @param \Drupal\Core\Mail\MailManagerInterface $mail_manager
|
||||
* The mail manager.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Egulias\EmailValidator\EmailValidator $email_validator
|
||||
* The email validator.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token, EntityManagerInterface $entity_manager, LoggerInterface $logger, MailManagerInterface $mail_manager, LanguageManagerInterface $language_manager, EmailValidator $email_validator) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->token = $token;
|
||||
$this->storage = $entity_manager->getStorage('user');
|
||||
$this->logger = $logger;
|
||||
$this->mailManager = $mail_manager;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->emailValidator = $email_validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition,
|
||||
$container->get('token'),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('logger.factory')->get('action'),
|
||||
$container->get('plugin.manager.mail'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('email.validator')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
if (empty($this->configuration['node'])) {
|
||||
$this->configuration['node'] = $entity;
|
||||
}
|
||||
|
||||
$recipient = PlainTextOutput::renderFromHtml($this->token->replace($this->configuration['recipient'], $this->configuration));
|
||||
|
||||
// If the recipient is a registered user with a language preference, use
|
||||
// the recipient's preferred language. Otherwise, use the system default
|
||||
// language.
|
||||
$recipient_accounts = $this->storage->loadByProperties(['mail' => $recipient]);
|
||||
$recipient_account = reset($recipient_accounts);
|
||||
if ($recipient_account) {
|
||||
$langcode = $recipient_account->getPreferredLangcode();
|
||||
}
|
||||
else {
|
||||
$langcode = $this->languageManager->getDefaultLanguage()->getId();
|
||||
}
|
||||
$params = ['context' => $this->configuration];
|
||||
|
||||
$message = $this->mailManager->mail('system', 'action_send_email', $recipient, $langcode, $params);
|
||||
// Error logging is handled by \Drupal\Core\Mail\MailManager::mail().
|
||||
if ($message['result']) {
|
||||
$this->logger->notice('Sent email to %recipient', ['%recipient' => $recipient]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'recipient' => '',
|
||||
'subject' => '',
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['recipient'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Recipient email address'),
|
||||
'#default_value' => $this->configuration['recipient'],
|
||||
'#maxlength' => '254',
|
||||
'#description' => t('You may also use tokens: [node:author:mail], [comment:author:mail], etc. Separate recipients with a comma.'),
|
||||
];
|
||||
$form['subject'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Subject'),
|
||||
'#default_value' => $this->configuration['subject'],
|
||||
'#maxlength' => '254',
|
||||
'#description' => t('The subject of the message.'),
|
||||
];
|
||||
$form['message'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('Message'),
|
||||
'#default_value' => $this->configuration['message'],
|
||||
'#cols' => '80',
|
||||
'#rows' => '20',
|
||||
'#description' => t('The message that should be sent. You may include placeholders like [node:title], [user:account-name], [user:display-name] and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
if (!$this->emailValidator->isValid($form_state->getValue('recipient')) && strpos($form_state->getValue('recipient'), ':mail') === FALSE) {
|
||||
// We want the literal %author placeholder to be emphasized in the error message.
|
||||
$form_state->setErrorByName('recipient', t('Enter a valid email address or use a token email address such as %author.', ['%author' => '[node:author:mail]']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['recipient'] = $form_state->getValue('recipient');
|
||||
$this->configuration['subject'] = $form_state->getValue('subject');
|
||||
$this->configuration['message'] = $form_state->getValue('message');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = AccessResult::allowed();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
140
2017/web/core/modules/action/src/Plugin/Action/GotoAction.php
Normal file
140
2017/web/core/modules/action/src/Plugin/Action/GotoAction.php
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action\Plugin\Action;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\ConfigurableActionBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Utility\UnroutedUrlAssemblerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* Redirects to a different URL.
|
||||
*
|
||||
* @Action(
|
||||
* id = "action_goto_action",
|
||||
* label = @Translation("Redirect to URL"),
|
||||
* type = "system"
|
||||
* )
|
||||
*/
|
||||
class GotoAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The event dispatcher service.
|
||||
*
|
||||
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $dispatcher;
|
||||
|
||||
/**
|
||||
* The unrouted URL assembler service.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface
|
||||
*/
|
||||
protected $unroutedUrlAssembler;
|
||||
|
||||
/**
|
||||
* Constructs a GotoAction 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 \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
|
||||
* The tempstore factory.
|
||||
* @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface $url_assembler
|
||||
* The unrouted URL assembler service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $dispatcher, UnroutedUrlAssemblerInterface $url_assembler) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->dispatcher = $dispatcher;
|
||||
$this->unroutedUrlAssembler = $url_assembler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition, $container->get('event_dispatcher'), $container->get('unrouted_url_assembler'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($object = NULL) {
|
||||
$url = $this->configuration['url'];
|
||||
// Leave external URLs unchanged, and assemble others as absolute URLs
|
||||
// relative to the site's base URL.
|
||||
if (!UrlHelper::isExternal($url)) {
|
||||
$parts = UrlHelper::parse($url);
|
||||
// @todo '<front>' is valid input for BC reasons, may be removed by
|
||||
// https://www.drupal.org/node/2421941
|
||||
if ($parts['path'] === '<front>') {
|
||||
$parts['path'] = '';
|
||||
}
|
||||
$uri = 'base:' . $parts['path'];
|
||||
$options = [
|
||||
'query' => $parts['query'],
|
||||
'fragment' => $parts['fragment'],
|
||||
'absolute' => TRUE,
|
||||
];
|
||||
// Treat this as if it's user input of a path relative to the site's
|
||||
// base URL.
|
||||
$url = $this->unroutedUrlAssembler->assemble($uri, $options);
|
||||
}
|
||||
$response = new RedirectResponse($url);
|
||||
$listener = function ($event) use ($response) {
|
||||
$event->setResponse($response);
|
||||
};
|
||||
// Add the listener to the event dispatcher.
|
||||
$this->dispatcher->addListener(KernelEvents::RESPONSE, $listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'url' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['url'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('URL'),
|
||||
'#description' => t('The URL to which the user should be redirected. This can be an internal URL like /node/1234 or an external URL like @url.', ['@url' => 'http://example.com']),
|
||||
'#default_value' => $this->configuration['url'],
|
||||
'#required' => TRUE,
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['url'] = $form_state->getValue('url');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$access = AccessResult::allowed();
|
||||
return $return_as_object ? $access : $access->isAllowed();
|
||||
}
|
||||
|
||||
}
|
134
2017/web/core/modules/action/src/Plugin/Action/MessageAction.php
Normal file
134
2017/web/core/modules/action/src/Plugin/Action/MessageAction.php
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\ConfigurableActionBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Utility\Token;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Sends a message to the current user's screen.
|
||||
*
|
||||
* @Action(
|
||||
* id = "action_message_action",
|
||||
* label = @Translation("Display a message to the user"),
|
||||
* type = "system"
|
||||
* )
|
||||
*/
|
||||
class MessageAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The token service.
|
||||
*
|
||||
* @var \Drupal\Core\Utility\Token
|
||||
*/
|
||||
protected $token;
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* The messenger.
|
||||
*
|
||||
* @var \Drupal\Core\Messenger\MessengerInterface
|
||||
*/
|
||||
protected $messenger;
|
||||
|
||||
/**
|
||||
* Constructs a MessageAction 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\Utility\Token $token
|
||||
* The token service.
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
|
||||
* The messenger.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, Token $token, RendererInterface $renderer, MessengerInterface $messenger) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->token = $token;
|
||||
$this->renderer = $renderer;
|
||||
$this->messenger = $messenger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($configuration, $plugin_id, $plugin_definition, $container->get('token'), $container->get('renderer'), $container->get('messenger'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute($entity = NULL) {
|
||||
if (empty($this->configuration['node'])) {
|
||||
$this->configuration['node'] = $entity;
|
||||
}
|
||||
$message = $this->token->replace($this->configuration['message'], $this->configuration);
|
||||
$build = [
|
||||
'#markup' => $message,
|
||||
];
|
||||
|
||||
// @todo Fix in https://www.drupal.org/node/2577827
|
||||
$this->messenger->addStatus($this->renderer->renderPlain($build));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'message' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['message'] = [
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('Message'),
|
||||
'#default_value' => $this->configuration['message'],
|
||||
'#required' => TRUE,
|
||||
'#rows' => '8',
|
||||
'#description' => t('The message to be displayed to the current user. You may include placeholders like [node:title], [user:account-name], [user:display-name] and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['message'] = $form_state->getValue('message');
|
||||
unset($this->configuration['node']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = AccessResult::allowed();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Drupal action source from database.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "action",
|
||||
* source_module = "system"
|
||||
* )
|
||||
*/
|
||||
class Action extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
return $this->select('actions', 'a')
|
||||
->fields('a');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'aid' => $this->t('Action ID'),
|
||||
'type' => $this->t('Module'),
|
||||
'callback' => $this->t('Callback function'),
|
||||
'parameters' => $this->t('Action configuration'),
|
||||
];
|
||||
if ($this->getModuleSchemaVersion('system') >= 7000) {
|
||||
$fields['label'] = $this->t('Label of the action');
|
||||
}
|
||||
else {
|
||||
$fields['description'] = $this->t('Action description');
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['aid']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
$aid = $row->getSourceProperty('aid');
|
||||
if (is_numeric($aid)) {
|
||||
if ($this->getModuleSchemaVersion('system') >= 7000) {
|
||||
$label = $row->getSourceProperty('label');
|
||||
}
|
||||
else {
|
||||
$label = $row->getSourceProperty('description');
|
||||
}
|
||||
$row->setSourceProperty('aid', $label);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
name: 'Action bulk form test'
|
||||
type: module
|
||||
description: 'Support module for action bulk form testing.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- drupal:action
|
||||
- drupal:views
|
||||
- drupal:node
|
|
@ -0,0 +1,181 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
- user
|
||||
id: test_bulk_form
|
||||
label: form
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: node_field_data
|
||||
base_field: nid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
cache:
|
||||
type: tag
|
||||
query:
|
||||
type: views_query
|
||||
exposed_form:
|
||||
type: basic
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 10
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: false
|
||||
summary: ''
|
||||
columns:
|
||||
title: title
|
||||
node_bulk_form: node_bulk_form
|
||||
info:
|
||||
title:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
bulk_form:
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
default: ''
|
||||
empty_table: false
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: node_field_data
|
||||
field: title
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
plugin_id: field
|
||||
entity_type: node
|
||||
entity_field: title
|
||||
node_bulk_form:
|
||||
id: node_bulk_form
|
||||
table: node
|
||||
field: node_bulk_form
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Bulk form'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: node_bulk_form
|
||||
entity_type: node
|
||||
filters:
|
||||
status:
|
||||
value: '1'
|
||||
table: node_field_data
|
||||
field: status
|
||||
id: status
|
||||
expose:
|
||||
operator: ''
|
||||
group: 1
|
||||
plugin_id: boolean
|
||||
entity_type: node
|
||||
entity_field: status
|
||||
sorts:
|
||||
created:
|
||||
id: created
|
||||
table: node_field_data
|
||||
field: created
|
||||
order: DESC
|
||||
plugin_id: date
|
||||
entity_type: node
|
||||
entity_field: created
|
||||
title: form
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: null
|
||||
display_options:
|
||||
path: test_bulk_form
|
||||
page_2:
|
||||
display_plugin: page
|
||||
id: page_2
|
||||
display_title: Page
|
||||
position: null
|
||||
display_options:
|
||||
path: test_bulk_form_empty
|
||||
defaults:
|
||||
style: false
|
||||
empty: false
|
||||
style:
|
||||
type: default
|
||||
empty:
|
||||
area:
|
||||
id: area
|
||||
table: views
|
||||
field: area_text_custom
|
||||
empty: true
|
||||
content: 'This view is empty.'
|
||||
plugin_id: text_custom
|
|
@ -0,0 +1,7 @@
|
|||
name: action_form_ajax_test
|
||||
type: module
|
||||
description: 'module used for testing ajax in action config entity forms.'
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
hidden: true
|
|
@ -0,0 +1,7 @@
|
|||
action.configuration.action_form_ajax_test:
|
||||
type: action_configuration_default
|
||||
label: 'action_form_ajax_test action'
|
||||
mapping:
|
||||
party_time:
|
||||
type: string
|
||||
label: 'The time of the party.'
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action_form_ajax_test\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Action\ConfigurableActionBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Plugin used for testing AJAX in action config entity forms.
|
||||
*
|
||||
* @Action(
|
||||
* id = "action_form_ajax_test",
|
||||
* label = @Translation("action_form_ajax_test"),
|
||||
* type = "system"
|
||||
* )
|
||||
*/
|
||||
class ActionAjaxTest extends ConfigurableActionBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'party_time' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$result = AccessResult::allowed();
|
||||
return $return_as_object ? $result : $result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function execute() {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$having_a_party = $form_state->getValue('having_a_party', !empty($this->configuration['party_time']));
|
||||
$form['having_a_party'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Are we having a party?'),
|
||||
'#ajax' => [
|
||||
'wrapper' => 'party-container',
|
||||
'callback' => [$this, 'partyCallback'],
|
||||
],
|
||||
'#default_value' => $having_a_party,
|
||||
];
|
||||
$form['container'] = [
|
||||
'#type' => 'container',
|
||||
'#prefix' => '<div id="party-container">',
|
||||
'#suffix' => '</div>',
|
||||
];
|
||||
|
||||
if ($having_a_party) {
|
||||
$form['container']['party_time'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Party time'),
|
||||
'#default_value' => $this->configuration['party_time'],
|
||||
];
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for party checkbox.
|
||||
*/
|
||||
public function partyCallback(array $form, FormStateInterface $form_state) {
|
||||
return $form['container'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->configuration['party_time'] = $form_state->getValue('party_time');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test behaviors when visiting the action listing page.
|
||||
*
|
||||
* @group action
|
||||
*/
|
||||
class ActionListTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['action'];
|
||||
|
||||
/**
|
||||
* Tests the behavior when there are no actions to list in the admin page.
|
||||
*/
|
||||
public function testEmptyActionList() {
|
||||
// Create a user with permission to view the actions administration pages.
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer actions']));
|
||||
|
||||
// Ensure the empty text appears on the action list page.
|
||||
/** @var $storage \Drupal\Core\Entity\EntityStorageInterface */
|
||||
$storage = $this->container->get('entity.manager')->getStorage('action');
|
||||
$actions = $storage->loadMultiple();
|
||||
$storage->delete($actions);
|
||||
$this->drupalGet('/admin/config/system/actions');
|
||||
$this->assertRaw('There are no actions yet.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests that uninstalling actions does not remove other module's actions.
|
||||
*
|
||||
* @group action
|
||||
* @see \Drupal\action\Plugin\views\field\BulkForm
|
||||
*/
|
||||
class ActionUninstallTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['views', 'action'];
|
||||
|
||||
/**
|
||||
* Tests Action uninstall.
|
||||
*/
|
||||
public function testActionUninstall() {
|
||||
\Drupal::service('module_installer')->uninstall(['action']);
|
||||
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('action');
|
||||
$storage->resetCache(['user_block_user_action']);
|
||||
$this->assertTrue($storage->load('user_block_user_action'), 'Configuration entity \'user_block_user_action\' still exists after uninstalling action module.');
|
||||
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->drupalGet('admin/people');
|
||||
// Ensure we have the user_block_user_action listed.
|
||||
$this->assertRaw('<option value="user_block_user_action">Block the selected user(s)</option>');
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests the views bulk form test.
|
||||
*
|
||||
* @group action
|
||||
* @see \Drupal\action\Plugin\views\field\BulkForm
|
||||
*/
|
||||
class BulkFormTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['node', 'action_bulk_test'];
|
||||
|
||||
/**
|
||||
* Tests the bulk form.
|
||||
*/
|
||||
public function testBulkForm() {
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
|
||||
// First, test an empty bulk form with the default style plugin to make sure
|
||||
// the empty region is rendered correctly.
|
||||
$this->drupalGet('test_bulk_form_empty');
|
||||
$this->assertText(t('This view is empty.'), 'Empty text found on empty bulk form.');
|
||||
|
||||
$nodes = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
// Ensure nodes are sorted in the same order they are inserted in the
|
||||
// array.
|
||||
$timestamp = REQUEST_TIME - $i;
|
||||
$nodes[] = $this->drupalCreateNode([
|
||||
'sticky' => FALSE,
|
||||
'created' => $timestamp,
|
||||
'changed' => $timestamp,
|
||||
]);
|
||||
}
|
||||
|
||||
$this->drupalGet('test_bulk_form');
|
||||
|
||||
// Test that the views edit header appears first.
|
||||
$first_form_element = $this->xpath('//form/div[1][@id = :id]', [':id' => 'edit-header']);
|
||||
$this->assertTrue($first_form_element, 'The views form edit header appears first.');
|
||||
|
||||
$this->assertFieldById('edit-action', NULL, 'The action select field appears.');
|
||||
|
||||
// Make sure a checkbox appears on all rows.
|
||||
$edit = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$this->assertFieldById('edit-node-bulk-form-' . $i, NULL, format_string('The checkbox on row @row appears.', ['@row' => $i]));
|
||||
$edit["node_bulk_form[$i]"] = TRUE;
|
||||
}
|
||||
|
||||
// Log in as a user with 'administer nodes' permission to have access to the
|
||||
// bulk operation.
|
||||
$this->drupalCreateContentType(['type' => 'page']);
|
||||
$admin_user = $this->drupalCreateUser(['administer nodes', 'edit any page content', 'delete any page content']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->drupalGet('test_bulk_form');
|
||||
|
||||
// Set all nodes to sticky and check that.
|
||||
$edit += ['action' => 'node_make_sticky_action'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$changed_node = $node_storage->load($node->id());
|
||||
$this->assertTrue($changed_node->isSticky(), format_string('Node @nid got marked as sticky.', ['@nid' => $node->id()]));
|
||||
}
|
||||
|
||||
$this->assertText('Make content sticky was applied to 10 items.');
|
||||
|
||||
// Unpublish just one node.
|
||||
$node = $node_storage->load($nodes[0]->id());
|
||||
$this->assertTrue($node->isPublished(), 'The node is published.');
|
||||
|
||||
$edit = ['node_bulk_form[0]' => TRUE, 'action' => 'node_unpublish_action'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
|
||||
|
||||
$this->assertText('Unpublish content was applied to 1 item.');
|
||||
|
||||
// Load the node again.
|
||||
$node_storage->resetCache([$node->id()]);
|
||||
$node = $node_storage->load($node->id());
|
||||
$this->assertFalse($node->isPublished(), 'A single node has been unpublished.');
|
||||
|
||||
// The second node should still be published.
|
||||
$node_storage->resetCache([$nodes[1]->id()]);
|
||||
$node = $node_storage->load($nodes[1]->id());
|
||||
$this->assertTrue($node->isPublished(), 'An unchecked node is still published.');
|
||||
|
||||
// Set up to include just the sticky actions.
|
||||
$view = Views::getView('test_bulk_form');
|
||||
$display = &$view->storage->getDisplay('default');
|
||||
$display['display_options']['fields']['node_bulk_form']['include_exclude'] = 'include';
|
||||
$display['display_options']['fields']['node_bulk_form']['selected_actions']['node_make_sticky_action'] = 'node_make_sticky_action';
|
||||
$display['display_options']['fields']['node_bulk_form']['selected_actions']['node_make_unsticky_action'] = 'node_make_unsticky_action';
|
||||
$view->save();
|
||||
|
||||
$this->drupalGet('test_bulk_form');
|
||||
$options = $this->xpath('//select[@id=:id]/option', [':id' => 'edit-action']);
|
||||
$this->assertEqual(count($options), 2);
|
||||
$this->assertOption('edit-action', 'node_make_sticky_action');
|
||||
$this->assertOption('edit-action', 'node_make_unsticky_action');
|
||||
|
||||
// Set up to exclude the sticky actions.
|
||||
$view = Views::getView('test_bulk_form');
|
||||
$display = &$view->storage->getDisplay('default');
|
||||
$display['display_options']['fields']['node_bulk_form']['include_exclude'] = 'exclude';
|
||||
$view->save();
|
||||
|
||||
$this->drupalGet('test_bulk_form');
|
||||
$this->assertNoOption('edit-action', 'node_make_sticky_action');
|
||||
$this->assertNoOption('edit-action', 'node_make_unsticky_action');
|
||||
|
||||
// Check the default title.
|
||||
$this->drupalGet('test_bulk_form');
|
||||
$result = $this->xpath('//label[@for="edit-action"]');
|
||||
$this->assertEqual('Action', $result[0]->getText());
|
||||
|
||||
// Setup up a different bulk form title.
|
||||
$view = Views::getView('test_bulk_form');
|
||||
$display = &$view->storage->getDisplay('default');
|
||||
$display['display_options']['fields']['node_bulk_form']['action_title'] = 'Test title';
|
||||
$view->save();
|
||||
|
||||
$this->drupalGet('test_bulk_form');
|
||||
$result = $this->xpath('//label[@for="edit-action"]');
|
||||
$this->assertEqual('Test title', $result[0]->getText());
|
||||
|
||||
$this->drupalGet('test_bulk_form');
|
||||
// Call the node delete action.
|
||||
$edit = [];
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$edit["node_bulk_form[$i]"] = TRUE;
|
||||
}
|
||||
$edit += ['action' => 'node_delete_action'];
|
||||
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
|
||||
// Make sure we don't show an action message while we are still on the
|
||||
// confirmation page.
|
||||
$errors = $this->xpath('//div[contains(@class, "messages--status")]');
|
||||
$this->assertFalse($errors, 'No action message shown.');
|
||||
$this->drupalPostForm(NULL, [], t('Delete'));
|
||||
$this->assertText(t('Deleted 5 content items.'));
|
||||
// Check if we got redirected to the original page.
|
||||
$this->assertUrl('test_bulk_form');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Functional;
|
||||
|
||||
use Drupal\system\Entity\Action;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests complex actions configuration by adding, editing, and deleting a
|
||||
* complex action.
|
||||
*
|
||||
* @group action
|
||||
*/
|
||||
class ConfigurationTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['action'];
|
||||
|
||||
/**
|
||||
* Tests configuration of advanced actions through administration interface.
|
||||
*/
|
||||
public function testActionConfiguration() {
|
||||
// Create a user with permission to view the actions administration pages.
|
||||
$user = $this->drupalCreateUser(['administer actions']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Make a POST request to admin/config/system/actions.
|
||||
$edit = [];
|
||||
$edit['action'] = 'action_goto_action';
|
||||
$this->drupalPostForm('admin/config/system/actions', $edit, t('Create'));
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Make a POST request to the individual action configuration page.
|
||||
$edit = [];
|
||||
$action_label = $this->randomMachineName();
|
||||
$edit['label'] = $action_label;
|
||||
$edit['id'] = strtolower($action_label);
|
||||
$edit['url'] = 'admin';
|
||||
$this->drupalPostForm('admin/config/system/actions/add/action_goto_action', $edit, t('Save'));
|
||||
$this->assertResponse(200);
|
||||
|
||||
$action_id = $edit['id'];
|
||||
|
||||
// Make sure that the new complex action was saved properly.
|
||||
$this->assertText(t('The action has been successfully saved.'), "Make sure we get a confirmation that we've successfully saved the complex action.");
|
||||
$this->assertText($action_label, "Make sure the action label appears on the configuration page after we've saved the complex action.");
|
||||
|
||||
// Make another POST request to the action edit page.
|
||||
$this->clickLink(t('Configure'));
|
||||
|
||||
$edit = [];
|
||||
$new_action_label = $this->randomMachineName();
|
||||
$edit['label'] = $new_action_label;
|
||||
$edit['url'] = 'admin';
|
||||
$this->drupalPostForm(NULL, $edit, t('Save'));
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Make sure that the action updated properly.
|
||||
$this->assertText(t('The action has been successfully saved.'), "Make sure we get a confirmation that we've successfully updated the complex action.");
|
||||
$this->assertNoText($action_label, "Make sure the old action label does NOT appear on the configuration page after we've updated the complex action.");
|
||||
$this->assertText($new_action_label, "Make sure the action label appears on the configuration page after we've updated the complex action.");
|
||||
|
||||
$this->clickLink(t('Configure'));
|
||||
$element = $this->xpath('//input[@type="text" and @value="admin"]');
|
||||
$this->assertTrue(!empty($element), 'Make sure the URL appears when re-editing the action.');
|
||||
|
||||
// Make sure that deletions work properly.
|
||||
$this->drupalGet('admin/config/system/actions');
|
||||
$this->clickLink(t('Delete'));
|
||||
$this->assertResponse(200);
|
||||
$edit = [];
|
||||
$this->drupalPostForm(NULL, $edit, t('Delete'));
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Make sure that the action was actually deleted.
|
||||
$this->assertRaw(t('The action %action has been deleted.', ['%action' => $new_action_label]), 'Make sure that we get a delete confirmation message.');
|
||||
$this->drupalGet('admin/config/system/actions');
|
||||
$this->assertResponse(200);
|
||||
$this->assertNoText($new_action_label, "Make sure the action label does not appear on the overview page after we've deleted the action.");
|
||||
|
||||
$action = Action::load($action_id);
|
||||
$this->assertFalse($action, 'Make sure the action is gone after being deleted.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\system\Entity\Action;
|
||||
|
||||
/**
|
||||
* Tests action plugins using Javascript.
|
||||
*
|
||||
* @group action
|
||||
*/
|
||||
class ActionFormAjaxTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['action', 'action_form_ajax_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$user = $this->drupalCreateUser(['administer actions']);
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests action plugins with AJAX save their configuration.
|
||||
*/
|
||||
public function testActionConfigurationWithAjax() {
|
||||
$url = Url::fromRoute('action.admin_add', ['action_id' => 'action_form_ajax_test']);
|
||||
$this->drupalGet($url);
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$id = 'test_plugin';
|
||||
$this->assertSession()->waitForElementVisible('named', ['button', 'Edit'])->press();
|
||||
$this->assertSession()->waitForElementVisible('css', '[name="id"]')->setValue($id);
|
||||
|
||||
$page->find('css', '[name="having_a_party"]')
|
||||
->check();
|
||||
$this->assertSession()->waitForElementVisible('css', '[name="party_time"]');
|
||||
|
||||
$party_time = 'Evening';
|
||||
$page->find('css', '[name="party_time"]')
|
||||
->setValue($party_time);
|
||||
|
||||
$page->find('css', '[value="Save"]')
|
||||
->click();
|
||||
|
||||
$url = Url::fromRoute('entity.action.collection');
|
||||
$this->assertSession()->pageTextContains('The action has been successfully saved.');
|
||||
$this->assertSession()->addressEquals($url);
|
||||
|
||||
// Check storage.
|
||||
$instance = Action::load($id);
|
||||
$configuration = $instance->getPlugin()->getConfiguration();
|
||||
$this->assertEquals(['party_time' => $party_time], $configuration);
|
||||
|
||||
// Configuration should be shown in edit form.
|
||||
$this->drupalGet($instance->toUrl('edit-form'));
|
||||
$this->assertSession()->checkboxChecked('having_a_party');
|
||||
$this->assertSession()->fieldValueEquals('party_time', $party_time);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Upgrade variables to action.settings.yml.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateActionConfigsTest extends MigrateDrupal6TestBase {
|
||||
|
||||
use SchemaCheckTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['action'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->executeMigration('action_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migration of action variables to action.settings.yml.
|
||||
*/
|
||||
public function testActionSettings() {
|
||||
$config = $this->config('action.settings');
|
||||
$this->assertIdentical(35, $config->get('recursion_limit'));
|
||||
$this->assertConfigSchema(\Drupal::service('config.typed'), 'action.settings', $config->get());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\system\Entity\Action;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Tests migration of action items.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateActionsTest extends MigrateDrupal6TestBase {
|
||||
|
||||
public static $modules = ['action', 'comment', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->executeMigration('d6_action');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Drupal 6 action migration to Drupal 8.
|
||||
*/
|
||||
public function testActions() {
|
||||
// Test default actions.
|
||||
$this->assertEntity('node_publish_action', 'Publish post', 'node', []);
|
||||
$this->assertEntity('node_make_sticky_action', 'Make post sticky', 'node', []);
|
||||
$this->assertEntity('user_block_user_action', 'Block current user', 'user', []);
|
||||
$this->assertEntity('comment_publish_action', 'Publish comment', 'comment', []);
|
||||
|
||||
// Test advanced actions.
|
||||
$this->assertEntity('unpublish_comment_containing_keyword_s_', 'Unpublish comment containing keyword(s)', 'comment', ["keywords" => [0 => "drupal"]]);
|
||||
$this->assertEntity('change_the_author_of_a_post', 'Change the author of a post', 'node', ["owner_uid" => "2"]);
|
||||
$this->assertEntity('unpublish_post_containing_keyword_s_', 'Unpublish post containing keyword(s)', 'node', ["keywords" => [0 => "drupal"]]);
|
||||
$this->assertEntity('display_a_message_to_the_user', 'Display a message to the user', 'system', ["message" => "Drupal migration test"]);
|
||||
$this->assertEntity('send_e_mail', 'Send e-mail', 'system', [
|
||||
"recipient" => "test@example.com",
|
||||
"subject" => "Drupal migration test",
|
||||
"message" => "Drupal migration test",
|
||||
]);
|
||||
$this->assertEntity('redirect_to_url', 'Redirect to URL', 'system', ["url" => "https://www.drupal.org"]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts various aspects of an Action entity.
|
||||
*
|
||||
* @param string $id
|
||||
* The expected Action ID.
|
||||
* @param string $label
|
||||
* The expected Action label.
|
||||
* @param string $type
|
||||
* The expected Action type.
|
||||
* @param array $configuration
|
||||
* The expected Action configuration.
|
||||
*/
|
||||
protected function assertEntity($id, $label, $type, $configuration) {
|
||||
$action = Action::load($id);
|
||||
|
||||
$this->assertTrue($action instanceof Action);
|
||||
/** @var \Drupal\system\Entity\Action $action */
|
||||
$this->assertIdentical($id, $action->id());
|
||||
$this->assertIdentical($label, $action->label());
|
||||
$this->assertIdentical($type, $action->getType());
|
||||
$this->assertIdentical($configuration, $action->get('configuration'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Upgrade variables to action.settings.yml.
|
||||
*
|
||||
* @group migrate_drupal_7
|
||||
*/
|
||||
class MigrateActionConfigsTest extends MigrateDrupal7TestBase {
|
||||
|
||||
use SchemaCheckTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['action'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->executeMigration('action_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migration of action variables to action.settings.yml.
|
||||
*/
|
||||
public function testActionSettings() {
|
||||
$config = $this->config('action.settings');
|
||||
$this->assertSame(28, $config->get('recursion_limit'));
|
||||
$this->assertConfigSchema(\Drupal::service('config.typed'), 'action.settings', $config->get());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\system\Entity\Action;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Tests migration of action items.
|
||||
*
|
||||
* @group action
|
||||
*/
|
||||
class MigrateActionsTest extends MigrateDrupal7TestBase {
|
||||
|
||||
public static $modules = ['action', 'comment', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->executeMigration('d7_action');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Drupal 7 action migration to Drupal 8.
|
||||
*/
|
||||
public function testActions() {
|
||||
// Test default actions.
|
||||
$this->assertEntity('node_publish_action', 'Publish content', 'node', []);
|
||||
$this->assertEntity('node_make_sticky_action', 'Make content sticky', 'node', []);
|
||||
$this->assertEntity('user_block_user_action', 'Block current user', 'user', []);
|
||||
$this->assertEntity('comment_publish_action', 'Publish comment', 'comment', []);
|
||||
|
||||
// Test advanced actions.
|
||||
$this->assertEntity('unpublish_comment_containing_keyword_s_', 'Unpublish comment containing keyword(s)', 'comment', ["keywords" => [0 => "drupal"]]);
|
||||
$this->assertEntity('change_the_author_of_content', 'Change the author of content', 'node', ["owner_uid" => "2"]);
|
||||
$this->assertEntity('unpublish_content_containing_keyword_s_', 'Unpublish content containing keyword(s)', 'node', ["keywords" => [0 => "drupal"]]);
|
||||
$this->assertEntity('display_a_message_to_the_user', 'Display a message to the user', 'system', ["message" => "Drupal migration test"]);
|
||||
$this->assertEntity('send_e_mail', 'Send e-mail', 'system', [
|
||||
"recipient" => "test@example.com",
|
||||
"subject" => "Drupal migration test",
|
||||
"message" => "Drupal migration test",
|
||||
]);
|
||||
$this->assertEntity('redirect_to_url', 'Redirect to URL', 'system', ["url" => "https://www.drupal.org"]);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts various aspects of an Action entity.
|
||||
*
|
||||
* @param string $id
|
||||
* The expected Action ID.
|
||||
* @param string $label
|
||||
* The expected Action label.
|
||||
* @param string $type
|
||||
* The expected Action type.
|
||||
* @param array $configuration
|
||||
* The expected Action configuration.
|
||||
*/
|
||||
protected function assertEntity($id, $label, $type, $configuration) {
|
||||
$action = Action::load($id);
|
||||
|
||||
$this->assertTrue($action instanceof Action);
|
||||
/** @var \Drupal\system\Entity\Action $action */
|
||||
$this->assertIdentical($id, $action->id());
|
||||
$this->assertIdentical($label, $action->label());
|
||||
$this->assertIdentical($type, $action->getType());
|
||||
$this->assertIdentical($configuration, $action->get('configuration'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Kernel\Plugin\Action;
|
||||
|
||||
use Drupal\Core\Test\AssertMailTrait;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests for the EmailAction plugin.
|
||||
*
|
||||
* @group action
|
||||
*/
|
||||
class EmailActionTest extends KernelTestBase {
|
||||
use AssertMailTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system', 'user', 'action', 'dblog'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
$this->installSchema('dblog', ['watchdog']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the email action plugin.
|
||||
*/
|
||||
public function testEmailAction() {
|
||||
/** @var \Drupal\Core\Action\ActionManager $plugin_manager */
|
||||
$plugin_manager = $this->container->get('plugin.manager.action');
|
||||
$configuration = [
|
||||
'recipient' => 'test@example.com',
|
||||
'subject' => 'Test subject',
|
||||
'message' => 'Test message',
|
||||
];
|
||||
$plugin_manager
|
||||
->createInstance('action_send_email_action', $configuration)
|
||||
->execute();
|
||||
|
||||
$mails = $this->getMails();
|
||||
$this->assertCount(1, $this->getMails());
|
||||
$this->assertEquals('test@example.com', $mails[0]['to']);
|
||||
$this->assertEquals('Test subject', $mails[0]['subject']);
|
||||
$this->assertEquals("Test message\n", $mails[0]['body']);
|
||||
|
||||
// Ensure that the email sending is logged.
|
||||
$log = \Drupal::database()
|
||||
->select('watchdog', 'w')
|
||||
->fields('w', ['message', 'variables'])
|
||||
->orderBy('wid', 'DESC')
|
||||
->range(0, 1)
|
||||
->execute()
|
||||
->fetch();
|
||||
|
||||
$this->assertEquals($log->message, 'Sent email to %recipient');
|
||||
$variables = unserialize($log->variables);
|
||||
$this->assertEquals($variables['%recipient'], 'test@example.com');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Kernel\Plugin\migrate\source;
|
||||
|
||||
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
|
||||
|
||||
/**
|
||||
* Tests actions source plugin.
|
||||
*
|
||||
* @covers \Drupal\action\Plugin\migrate\source\Action
|
||||
* @group action
|
||||
*/
|
||||
class ActionTest extends MigrateSqlSourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['action', 'migrate_drupal', 'system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function providerSource() {
|
||||
$tests = [];
|
||||
|
||||
$tests[0][0]['actions'] = [
|
||||
[
|
||||
'aid' => 'Redirect to node list page',
|
||||
'type' => 'system',
|
||||
'callback' => 'system_goto_action',
|
||||
'parameters' => 'a:1:{s:3:"url";s:4:"node";}',
|
||||
'description' => 'Redirect to node list page',
|
||||
],
|
||||
[
|
||||
'aid' => 'Test notice email',
|
||||
'type' => 'system',
|
||||
'callback' => 'system_send_email_action',
|
||||
'parameters' => 'a:3:{s:9:"recipient";s:7:"%author";s:7:"subject";s:4:"Test";s:7:"message";s:4:"Test',
|
||||
'description' => 'Test notice email',
|
||||
],
|
||||
[
|
||||
'aid' => 'comment_publish_action',
|
||||
'type' => 'comment',
|
||||
'callback' => 'comment_publish_action',
|
||||
'parameters' => NULL,
|
||||
'description' => NULL,
|
||||
],
|
||||
[
|
||||
'aid' => 'node_publish_action',
|
||||
'type' => 'comment',
|
||||
'callback' => 'node_publish_action',
|
||||
'parameters' => NULL,
|
||||
'description' => NULL,
|
||||
],
|
||||
];
|
||||
// The expected results are identical to the source data.
|
||||
$tests[0][1] = $tests[0][0]['actions'];
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\action\Unit\Menu;
|
||||
|
||||
use Drupal\Tests\Core\Menu\LocalTaskIntegrationTestBase;
|
||||
|
||||
/**
|
||||
* Tests action local tasks.
|
||||
*
|
||||
* @group action
|
||||
*/
|
||||
class ActionLocalTasksTest extends LocalTaskIntegrationTestBase {
|
||||
|
||||
protected function setUp() {
|
||||
$this->directoryList = ['action' => 'core/modules/action'];
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests local task existence.
|
||||
*/
|
||||
public function testActionLocalTasks() {
|
||||
$this->assertLocalTasks('entity.action.collection', [['action.admin']]);
|
||||
}
|
||||
|
||||
}
|
10
2017/web/core/modules/aggregator/aggregator.info.yml
Normal file
10
2017/web/core/modules/aggregator/aggregator.info.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: Aggregator
|
||||
type: module
|
||||
description: 'Aggregates syndicated content (RSS, RDF, and Atom feeds) from external sources.'
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
configure: aggregator.admin_settings
|
||||
dependencies:
|
||||
- drupal:file
|
||||
- drupal:options
|
52
2017/web/core/modules/aggregator/aggregator.install
Normal file
52
2017/web/core/modules/aggregator/aggregator.install
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the aggregator module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_requirements().
|
||||
*/
|
||||
function aggregator_requirements($phase) {
|
||||
$has_curl = function_exists('curl_init');
|
||||
$requirements = [];
|
||||
$requirements['curl'] = [
|
||||
'title' => t('cURL'),
|
||||
'value' => $has_curl ? t('Enabled') : t('Not found'),
|
||||
];
|
||||
if (!$has_curl) {
|
||||
$requirements['curl']['severity'] = REQUIREMENT_ERROR;
|
||||
$requirements['curl']['description'] = t('The Aggregator module requires the <a href="https://secure.php.net/manual/en/curl.setup.php">PHP cURL library</a>. For more information, see the <a href="https://www.drupal.org/requirements/php/curl">online information on installing the PHP cURL extension</a>.');
|
||||
}
|
||||
return $requirements;
|
||||
}
|
||||
|
||||
/**
|
||||
* The simple presence of this update function clears cached field definitions.
|
||||
*/
|
||||
function aggregator_update_8001() {
|
||||
// Feed ID base field is now required.
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the 'Source feed' field for aggregator items required.
|
||||
*/
|
||||
function aggregator_update_8200() {
|
||||
// aggregator_update_8001() did not update the last installed field storage
|
||||
// definition for the aggregator item's 'Source feed' field.
|
||||
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$field_definition = $definition_update_manager->getFieldStorageDefinition('fid', 'aggregator_item');
|
||||
$field_definition->setRequired(TRUE);
|
||||
$definition_update_manager->updateFieldStorageDefinition($field_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a default value for the 'Refresh' field for aggregator feed entities.
|
||||
*/
|
||||
function aggregator_update_8501() {
|
||||
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$field_definition = $definition_update_manager->getFieldStorageDefinition('refresh', 'aggregator_feed');
|
||||
$field_definition->setDefaultValue(3600);
|
||||
$definition_update_manager->updateFieldStorageDefinition($field_definition);
|
||||
}
|
11
2017/web/core/modules/aggregator/aggregator.links.action.yml
Normal file
11
2017/web/core/modules/aggregator/aggregator.links.action.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
aggregator.feed_add:
|
||||
route_name: aggregator.feed_add
|
||||
title: 'Add feed'
|
||||
appears_on:
|
||||
- 'aggregator.admin_overview'
|
||||
|
||||
aggregator.opml_add:
|
||||
route_name: aggregator.opml_add
|
||||
title: 'Import OPML'
|
||||
appears_on:
|
||||
- 'aggregator.admin_overview'
|
13
2017/web/core/modules/aggregator/aggregator.links.menu.yml
Normal file
13
2017/web/core/modules/aggregator/aggregator.links.menu.yml
Normal file
|
@ -0,0 +1,13 @@
|
|||
aggregator.admin_overview:
|
||||
title: 'Aggregator'
|
||||
description: 'Add feeds or import OPMLs to gather external content and configure how often they are updated.'
|
||||
route_name: aggregator.admin_overview
|
||||
parent: system.admin_config_services
|
||||
weight: 10
|
||||
aggregator.page_last:
|
||||
title: 'Aggregator'
|
||||
weight: 5
|
||||
route_name: aggregator.page_last
|
||||
aggregator.feed_add:
|
||||
title: 'Add feed'
|
||||
route_name: aggregator.feed_add
|
24
2017/web/core/modules/aggregator/aggregator.links.task.yml
Normal file
24
2017/web/core/modules/aggregator/aggregator.links.task.yml
Normal file
|
@ -0,0 +1,24 @@
|
|||
aggregator.admin_overview:
|
||||
route_name: aggregator.admin_overview
|
||||
title: 'List'
|
||||
base_route: aggregator.admin_overview
|
||||
aggregator.admin_settings:
|
||||
route_name: aggregator.admin_settings
|
||||
title: 'Settings'
|
||||
weight: 100
|
||||
base_route: aggregator.admin_overview
|
||||
|
||||
entity.aggregator_feed.canonical:
|
||||
route_name: entity.aggregator_feed.canonical
|
||||
base_route: entity.aggregator_feed.canonical
|
||||
title: View
|
||||
entity.aggregator_feed.edit_form:
|
||||
route_name: entity.aggregator_feed.edit_form
|
||||
base_route: entity.aggregator_feed.canonical
|
||||
title: 'Configure'
|
||||
weight: 10
|
||||
entity.aggregator_feed.delete_form:
|
||||
route_name: entity.aggregator_feed.delete_form
|
||||
base_route: entity.aggregator_feed.canonical
|
||||
title: Delete
|
||||
weight: 20
|
187
2017/web/core/modules/aggregator/aggregator.module
Normal file
187
2017/web/core/modules/aggregator/aggregator.module
Normal file
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Used to aggregate syndicated content (RSS, RDF, and Atom).
|
||||
*/
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
|
||||
/**
|
||||
* Denotes that a feed's items should never expire.
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\aggregator\FeedStorageInterface::CLEAR_NEVER instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2831620
|
||||
*/
|
||||
const AGGREGATOR_CLEAR_NEVER = 0;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function aggregator_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.aggregator':
|
||||
$path_validator = \Drupal::pathValidator();
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Aggregator module is an on-site syndicator and news reader that gathers and displays fresh content from RSS-, RDF-, and Atom-based feeds made available across the web. Thousands of sites (particularly news sites and blogs) publish their latest headlines in feeds, using a number of standardized XML-based formats. For more information, see the <a href=":aggregator-module">online documentation for the Aggregator module</a>.', [':aggregator-module' => 'https://www.drupal.org/documentation/modules/aggregator']) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
// Check if the aggregator sources View is enabled.
|
||||
if ($url = $path_validator->getUrlIfValid('aggregator/sources')) {
|
||||
$output .= '<dt>' . t('Viewing feeds') . '</dt>';
|
||||
$output .= '<dd>' . t('Users view feed content in the <a href=":aggregator">main aggregator display</a>, or by <a href=":aggregator-sources">their source</a> (usually via an RSS feed reader). The most recent content in a feed can be displayed as a block through the <a href=":admin-block">Blocks administration page</a>.', [':aggregator' => \Drupal::url('aggregator.page_last'), ':aggregator-sources' => $url->toString(), ':admin-block' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#']) . '</dd>';
|
||||
}
|
||||
$output .= '<dt>' . t('Adding, editing, and deleting feeds') . '</dt>';
|
||||
$output .= '<dd>' . t('Administrators can add, edit, and delete feeds, and choose how often to check each feed for newly updated items on the <a href=":feededit">Aggregator administration page</a>.', [':feededit' => \Drupal::url('aggregator.admin_overview')]) . '</dd>';
|
||||
$output .= '<dt>' . t('Configuring the display of feed items') . '</dt>';
|
||||
$output .= '<dd>' . t('Administrators can choose how many items are displayed in the listing pages, which HTML tags are allowed in the content of feed items, and whether they should be trimmed to a maximum number of characters on the <a href=":settings">Aggregator settings page</a>.', [':settings' => \Drupal::url('aggregator.admin_settings')]) . '</dd>';
|
||||
$output .= '<dt>' . t('Discarding old feed items') . '</dt>';
|
||||
$output .= '<dd>' . t('Administrators can choose whether to discard feed items that are older than a specified period of time on the <a href=":settings">Aggregator settings page</a>. This requires a correctly configured cron maintenance task (see below).', [':settings' => \Drupal::url('aggregator.admin_settings')]) . '<dd>';
|
||||
|
||||
$output .= '<dt>' . t('<abbr title="Outline Processor Markup Language">OPML</abbr> integration') . '</dt>';
|
||||
// Check if the aggregator opml View is enabled.
|
||||
if ($url = $path_validator->getUrlIfValid('aggregator/opml')) {
|
||||
$output .= '<dd>' . t('A <a href=":aggregator-opml">machine-readable OPML file</a> of all feeds is available. OPML is an XML-based file format used to share outline-structured information such as a list of RSS feeds. Feeds can also be <a href=":import-opml">imported via an OPML file</a>.', [':aggregator-opml' => $url->toString(), ':import-opml' => \Drupal::url('aggregator.opml_add')]) . '</dd>';
|
||||
}
|
||||
$output .= '<dt>' . t('Configuring cron') . '</dt>';
|
||||
$output .= '<dd>' . t('A working <a href=":cron">cron maintenance task</a> is required to update feeds automatically.', [':cron' => \Drupal::url('system.cron_settings')]) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
|
||||
case 'aggregator.admin_overview':
|
||||
// Don't use placeholders for possibility to change URLs for translators.
|
||||
$output = '<p>' . t('Many sites publish their headlines and posts in feeds, using a number of standardized XML-based formats. The aggregator supports <a href="http://en.wikipedia.org/wiki/Rss">RSS</a>, <a href="http://en.wikipedia.org/wiki/Resource_Description_Framework">RDF</a>, and <a href="http://en.wikipedia.org/wiki/Atom_%28standard%29">Atom</a>.') . '</p>';
|
||||
$output .= '<p>' . t('Current feeds are listed below, and <a href=":addfeed">new feeds may be added</a>. For each feed, the <em>latest items</em> block may be enabled at the <a href=":block">blocks administration page</a>.', [':addfeed' => \Drupal::url('aggregator.feed_add'), ':block' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#']) . '</p>';
|
||||
return $output;
|
||||
|
||||
case 'aggregator.feed_add':
|
||||
return '<p>' . t('Add a feed in RSS, RDF or Atom format. A feed may only have one entry.') . '</p>';
|
||||
|
||||
case 'aggregator.opml_add':
|
||||
return '<p>' . t('<abbr title="Outline Processor Markup Language">OPML</abbr> is an XML format for exchanging feeds between aggregators. A single OPML document may contain many feeds. Aggregator uses this file to import all feeds at once. Upload a file from your computer or enter a URL where the OPML file can be downloaded.') . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function aggregator_theme() {
|
||||
return [
|
||||
'aggregator_feed' => [
|
||||
'render element' => 'elements',
|
||||
'file' => 'aggregator.theme.inc',
|
||||
],
|
||||
'aggregator_item' => [
|
||||
'render element' => 'elements',
|
||||
'file' => 'aggregator.theme.inc',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_extra_field_info().
|
||||
*/
|
||||
function aggregator_entity_extra_field_info() {
|
||||
$extra = [];
|
||||
|
||||
$extra['aggregator_feed']['aggregator_feed'] = [
|
||||
'display' => [
|
||||
'items' => [
|
||||
'label' => t('Items'),
|
||||
'description' => t('Items associated with this feed'),
|
||||
'weight' => 0,
|
||||
],
|
||||
// @todo Move to a formatter at https://www.drupal.org/node/2339917.
|
||||
'image' => [
|
||||
'label' => t('Image'),
|
||||
'description' => t('The feed image'),
|
||||
'weight' => 2,
|
||||
],
|
||||
// @todo Move to a formatter at https://www.drupal.org/node/2149845.
|
||||
'description' => [
|
||||
'label' => t('Description'),
|
||||
'description' => t('The description of this feed'),
|
||||
'weight' => 3,
|
||||
],
|
||||
'more_link' => [
|
||||
'label' => t('More link'),
|
||||
'description' => t('A more link to the feed detail page'),
|
||||
'weight' => 5,
|
||||
],
|
||||
'feed_icon' => [
|
||||
'label' => t('Feed icon'),
|
||||
'description' => t('An icon that links to the feed URL'),
|
||||
'weight' => 6,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$extra['aggregator_item']['aggregator_item'] = [
|
||||
'display' => [
|
||||
// @todo Move to a formatter at https://www.drupal.org/node/2149845.
|
||||
'description' => [
|
||||
'label' => t('Description'),
|
||||
'description' => t('The description of this feed item'),
|
||||
'weight' => 2,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_cron().
|
||||
*
|
||||
* Queues news feeds for updates once their refresh interval has elapsed.
|
||||
*/
|
||||
function aggregator_cron() {
|
||||
$queue = \Drupal::queue('aggregator_feeds');
|
||||
|
||||
$ids = \Drupal::entityManager()->getStorage('aggregator_feed')->getFeedIdsToRefresh();
|
||||
foreach (Feed::loadMultiple($ids) as $feed) {
|
||||
if ($queue->createItem($feed)) {
|
||||
// Add timestamp to avoid queueing item more than once.
|
||||
$feed->setQueuedTime(REQUEST_TIME);
|
||||
$feed->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete queued timestamp after 6 hours assuming the update has failed.
|
||||
$ids = \Drupal::entityQuery('aggregator_feed')
|
||||
->condition('queued', REQUEST_TIME - (3600 * 6), '<')
|
||||
->execute();
|
||||
|
||||
if ($ids) {
|
||||
$feeds = Feed::loadMultiple($ids);
|
||||
foreach ($feeds as $feed) {
|
||||
$feed->setQueuedTime(0);
|
||||
$feed->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of allowed tags.
|
||||
*
|
||||
* @return array
|
||||
* The list of allowed tags.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function _aggregator_allowed_tags() {
|
||||
return preg_split('/\s+|<|>/', \Drupal::config('aggregator.settings')->get('items.allowed_html'), -1, PREG_SPLIT_NO_EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK() for block templates.
|
||||
*/
|
||||
function aggregator_preprocess_block(&$variables) {
|
||||
if ($variables['configuration']['provider'] == 'aggregator') {
|
||||
$variables['attributes']['role'] = 'complementary';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
administer news feeds:
|
||||
title: 'Administer news feeds'
|
||||
access news feeds:
|
||||
title: 'View news feeds'
|
58
2017/web/core/modules/aggregator/aggregator.routing.yml
Normal file
58
2017/web/core/modules/aggregator/aggregator.routing.yml
Normal file
|
@ -0,0 +1,58 @@
|
|||
aggregator.admin_overview:
|
||||
path: '/admin/config/services/aggregator'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator\Controller\AggregatorController::adminOverview'
|
||||
_title: 'Aggregator'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
|
||||
aggregator.admin_settings:
|
||||
path: '/admin/config/services/aggregator/settings'
|
||||
defaults:
|
||||
_form: '\Drupal\aggregator\Form\SettingsForm'
|
||||
_title: 'Aggregator settings'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
|
||||
aggregator.feed_items_delete:
|
||||
path: '/admin/config/services/aggregator/delete/{aggregator_feed}'
|
||||
defaults:
|
||||
_entity_form: 'aggregator_feed.delete_items'
|
||||
_title: 'Delete items'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
|
||||
aggregator.feed_refresh:
|
||||
path: '/admin/config/services/aggregator/update/{aggregator_feed}'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator\Controller\AggregatorController::feedRefresh'
|
||||
_title: 'Update items'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
_csrf_token: 'TRUE'
|
||||
|
||||
aggregator.opml_add:
|
||||
path: '/admin/config/services/aggregator/add/opml'
|
||||
defaults:
|
||||
_form: '\Drupal\aggregator\Form\OpmlFeedAdd'
|
||||
_title: 'Import OPML'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
|
||||
aggregator.feed_add:
|
||||
path: '/aggregator/sources/add'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator\Controller\AggregatorController::feedAdd'
|
||||
_title: 'Add feed'
|
||||
requirements:
|
||||
_permission: 'administer news feeds'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
|
||||
aggregator.page_last:
|
||||
path: '/aggregator'
|
||||
defaults:
|
||||
_controller: '\Drupal\aggregator\Controller\AggregatorController::pageLast'
|
||||
_title: 'Aggregator'
|
||||
requirements:
|
||||
_permission: 'access news feeds'
|
16
2017/web/core/modules/aggregator/aggregator.services.yml
Normal file
16
2017/web/core/modules/aggregator/aggregator.services.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
services:
|
||||
plugin.manager.aggregator.fetcher:
|
||||
class: Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
arguments: [fetcher, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
plugin.manager.aggregator.parser:
|
||||
class: Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
arguments: [parser, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
plugin.manager.aggregator.processor:
|
||||
class: Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
arguments: [processor, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
aggregator.items.importer:
|
||||
class: Drupal\aggregator\ItemsImporter
|
||||
arguments: ['@config.factory', '@plugin.manager.aggregator.fetcher', '@plugin.manager.aggregator.parser', '@plugin.manager.aggregator.processor', '@logger.channel.aggregator']
|
||||
logger.channel.aggregator:
|
||||
parent: logger.channel_base
|
||||
arguments: ['aggregator']
|
50
2017/web/core/modules/aggregator/aggregator.theme.inc
Normal file
50
2017/web/core/modules/aggregator/aggregator.theme.inc
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Preprocessors and theme functions of Aggregator module.
|
||||
*/
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
/**
|
||||
* Prepares variables for aggregator item templates.
|
||||
*
|
||||
* Default template: aggregator-item.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - elements: An array of elements to display in view mode.
|
||||
*/
|
||||
function template_preprocess_aggregator_item(&$variables) {
|
||||
$item = $variables['elements']['#aggregator_item'];
|
||||
|
||||
// Helpful $content variable for templates.
|
||||
foreach (Element::children($variables['elements']) as $key) {
|
||||
$variables['content'][$key] = $variables['elements'][$key];
|
||||
}
|
||||
|
||||
$variables['url'] = UrlHelper::stripDangerousProtocols($item->getLink());
|
||||
$variables['title'] = $item->label();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for aggregator feed templates.
|
||||
*
|
||||
* Default template: aggregator-feed.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - elements: An array of elements to display in view mode.
|
||||
*/
|
||||
function template_preprocess_aggregator_feed(&$variables) {
|
||||
$feed = $variables['elements']['#aggregator_feed'];
|
||||
|
||||
// Helpful $content variable for templates.
|
||||
foreach (Element::children($variables['elements']) as $key) {
|
||||
$variables['content'][$key] = $variables['elements'][$key];
|
||||
}
|
||||
$variables['full'] = $variables['elements']['#view_mode'] == 'full';
|
||||
$variables['title'] = $feed->label();
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
fetcher: aggregator
|
||||
parser: aggregator
|
||||
processors:
|
||||
- aggregator
|
||||
items:
|
||||
allowed_html: '<a> <b> <br> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>'
|
||||
teaser_length: 600
|
||||
expire: 9676800
|
||||
source:
|
||||
list_max: 3
|
|
@ -0,0 +1,38 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_feed.aggregator_feed.default
|
||||
targetEntityType: aggregator_feed
|
||||
bundle: aggregator_feed
|
||||
mode: default
|
||||
content:
|
||||
checked:
|
||||
type: timestamp_ago
|
||||
weight: 1
|
||||
region: content
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
label: inline
|
||||
description:
|
||||
weight: 3
|
||||
region: content
|
||||
feed_icon:
|
||||
weight: 5
|
||||
region: content
|
||||
image:
|
||||
weight: 2
|
||||
region: content
|
||||
items:
|
||||
weight: 0
|
||||
region: content
|
||||
link:
|
||||
type: uri_link
|
||||
weight: 4
|
||||
region: content
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
label: inline
|
||||
hidden:
|
||||
more_link: true
|
|
@ -0,0 +1,24 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.aggregator_feed.summary
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_feed.aggregator_feed.summary
|
||||
targetEntityType: aggregator_feed
|
||||
bundle: aggregator_feed
|
||||
mode: summary
|
||||
content:
|
||||
items:
|
||||
weight: 0
|
||||
region: content
|
||||
more_link:
|
||||
weight: 1
|
||||
region: content
|
||||
hidden:
|
||||
checked: true
|
||||
description: true
|
||||
feed_icon: true
|
||||
image: true
|
||||
link: true
|
|
@ -0,0 +1,20 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.aggregator_item.summary
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_item.aggregator_item.summary
|
||||
targetEntityType: aggregator_item
|
||||
bundle: aggregator_item
|
||||
mode: summary
|
||||
content:
|
||||
timestamp:
|
||||
weight: 0
|
||||
region: content
|
||||
hidden:
|
||||
author: true
|
||||
description: true
|
||||
feed: true
|
||||
link: true
|
|
@ -0,0 +1,9 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_feed.summary
|
||||
label: Summary
|
||||
targetEntityType: aggregator_feed
|
||||
cache: true
|
|
@ -0,0 +1,9 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- aggregator
|
||||
id: aggregator_item.summary
|
||||
label: Summary
|
||||
targetEntityType: aggregator_item
|
||||
cache: true
|
|
@ -0,0 +1,161 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- aggregator
|
||||
- user
|
||||
id: aggregator_rss_feed
|
||||
label: 'Aggregator RSS feed'
|
||||
module: aggregator
|
||||
description: ''
|
||||
tag: aggregator
|
||||
base_table: aggregator_item
|
||||
base_field: iid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access news feeds'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: 0
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
quantity: 9
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: 'entity:aggregator_item'
|
||||
fields:
|
||||
iid:
|
||||
table: aggregator_item
|
||||
field: iid
|
||||
id: iid
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Item ID'
|
||||
exclude: false
|
||||
plugin_id: field
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: number_integer
|
||||
entity_type: aggregator_item
|
||||
entity_field: iid
|
||||
filters: { }
|
||||
sorts: { }
|
||||
title: 'Aggregator RSS feed'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
cacheable: false
|
||||
max-age: -1
|
||||
tags: { }
|
||||
feed_items:
|
||||
display_plugin: feed
|
||||
id: feed_items
|
||||
display_title: Feed
|
||||
position: 1
|
||||
display_options:
|
||||
path: aggregator/rss
|
||||
display_description: ''
|
||||
defaults:
|
||||
arguments: true
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user.permissions
|
||||
cacheable: false
|
||||
max-age: -1
|
||||
tags: { }
|
|
@ -0,0 +1,429 @@
|
|||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- core.entity_view_mode.aggregator_feed.summary
|
||||
- system.menu.tools
|
||||
module:
|
||||
- aggregator
|
||||
- user
|
||||
id: aggregator_sources
|
||||
label: 'Aggregator sources'
|
||||
module: aggregator
|
||||
description: ''
|
||||
tag: aggregator
|
||||
base_table: aggregator_feed
|
||||
base_field: fid
|
||||
core: 8.x
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Master
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access news feeds'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
quantity: 9
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: 'entity:aggregator_feed'
|
||||
options:
|
||||
relationship: none
|
||||
view_mode: summary
|
||||
fields:
|
||||
fid:
|
||||
table: aggregator_feed
|
||||
field: fid
|
||||
id: fid
|
||||
plugin_id: field
|
||||
type: number_integer
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
entity_type: aggregator_feed
|
||||
entity_field: fid
|
||||
filters: { }
|
||||
sorts: { }
|
||||
title: Sources
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
max-age: -1
|
||||
tags: { }
|
||||
feed_1:
|
||||
display_plugin: feed
|
||||
id: feed_1
|
||||
display_title: Feed
|
||||
position: 2
|
||||
display_options:
|
||||
style:
|
||||
type: opml
|
||||
options:
|
||||
grouping: { }
|
||||
path: aggregator/opml
|
||||
fields:
|
||||
title:
|
||||
id: title
|
||||
table: aggregator_feed
|
||||
field: title
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: aggregator_title
|
||||
settings:
|
||||
display_as_link: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
plugin_id: field
|
||||
entity_type: aggregator_feed
|
||||
entity_field: title
|
||||
url:
|
||||
id: url
|
||||
table: aggregator_feed
|
||||
field: url
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
display_as_link: false
|
||||
plugin_id: url
|
||||
entity_type: aggregator_feed
|
||||
entity_field: url
|
||||
description:
|
||||
id: description
|
||||
table: aggregator_feed
|
||||
field: description
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: xss
|
||||
entity_type: aggregator_feed
|
||||
entity_field: description
|
||||
link:
|
||||
id: link
|
||||
table: aggregator_feed
|
||||
field: link
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
display_as_link: false
|
||||
plugin_id: url
|
||||
entity_type: aggregator_feed
|
||||
entity_field: link
|
||||
defaults:
|
||||
fields: false
|
||||
title: false
|
||||
row:
|
||||
type: opml_fields
|
||||
options:
|
||||
type_field: rss
|
||||
text_field: title
|
||||
created_field: ''
|
||||
description_field: description
|
||||
html_url_field: link
|
||||
language_field: ''
|
||||
xml_url_field: url
|
||||
url_field: ''
|
||||
displays:
|
||||
page_1: page_1
|
||||
default: '0'
|
||||
title: 'OPML feed'
|
||||
sitename_title: true
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- user.permissions
|
||||
max-age: -1
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
path: aggregator/sources
|
||||
menu:
|
||||
type: normal
|
||||
title: Sources
|
||||
description: ''
|
||||
weight: 0
|
||||
context: '0'
|
||||
menu_name: tools
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
max-age: -1
|
||||
tags: { }
|
|
@ -0,0 +1,57 @@
|
|||
# Schema for the configuration files of the aggregator module.
|
||||
|
||||
aggregator.settings:
|
||||
type: config_object
|
||||
label: 'Aggregator settings'
|
||||
mapping:
|
||||
fetcher:
|
||||
type: string
|
||||
label: 'Fetcher'
|
||||
parser:
|
||||
type: string
|
||||
label: 'Parser'
|
||||
processors:
|
||||
type: sequence
|
||||
label: 'Processors'
|
||||
sequence:
|
||||
type: string
|
||||
label: 'Processor'
|
||||
items:
|
||||
type: mapping
|
||||
label: 'Items'
|
||||
mapping:
|
||||
allowed_html:
|
||||
type: string
|
||||
label: 'Allowed HTML tags'
|
||||
teaser_length:
|
||||
type: integer
|
||||
label: 'Length of trimmed description'
|
||||
expire:
|
||||
type: integer
|
||||
label: 'Discard items older than'
|
||||
source:
|
||||
type: mapping
|
||||
label: 'Source'
|
||||
mapping:
|
||||
list_max:
|
||||
type: integer
|
||||
label: 'Number of items shown in listing pages'
|
||||
|
||||
block.settings.aggregator_feed_block:
|
||||
type: block_settings
|
||||
label: 'Aggregator feed block'
|
||||
mapping:
|
||||
block_count:
|
||||
type: integer
|
||||
label: 'Block count'
|
||||
feed:
|
||||
type: string
|
||||
label: 'Feed'
|
||||
|
||||
field.formatter.settings.aggregator_title:
|
||||
type: mapping
|
||||
label: 'Formatter settings'
|
||||
mapping:
|
||||
display_as_link:
|
||||
type: boolean
|
||||
label: 'Display as link'
|
|
@ -0,0 +1,24 @@
|
|||
# Schema for the views plugins of the Aggregator module.
|
||||
|
||||
views.argument.aggregator_fid:
|
||||
type: views.argument.numeric
|
||||
label: 'Aggregator feed ID'
|
||||
|
||||
views.argument.aggregator_iid:
|
||||
type: views.argument.numeric
|
||||
label: 'Aggregator item ID'
|
||||
|
||||
views.field.aggregator_title_link:
|
||||
type: views_field
|
||||
label: 'Title link'
|
||||
mapping:
|
||||
display_as_link:
|
||||
type: boolean
|
||||
|
||||
views.row.aggregator_rss:
|
||||
type: views_row
|
||||
label: 'Aggregator item row'
|
||||
mapping:
|
||||
view_mode:
|
||||
type: string
|
||||
label: 'Display type'
|
|
@ -0,0 +1,21 @@
|
|||
id: d6_aggregator_feed
|
||||
label: Aggregator feeds
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
source:
|
||||
plugin: aggregator_feed
|
||||
process:
|
||||
fid: fid
|
||||
title: title
|
||||
url: url
|
||||
refresh: refresh
|
||||
checked: checked
|
||||
link: link
|
||||
description: description
|
||||
image: image
|
||||
etag: etag
|
||||
modified: modified
|
||||
destination:
|
||||
plugin: entity:aggregator_feed
|
|
@ -0,0 +1,25 @@
|
|||
id: d6_aggregator_item
|
||||
label: Aggregator items
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
source:
|
||||
plugin: aggregator_item
|
||||
process:
|
||||
iid: iid
|
||||
fid:
|
||||
plugin: migration_lookup
|
||||
migration: d6_aggregator_feed
|
||||
source: fid
|
||||
title: title
|
||||
link: link
|
||||
author: author
|
||||
description: description
|
||||
timestamp: timestamp
|
||||
guid: guid
|
||||
destination:
|
||||
plugin: entity:aggregator_item
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_aggregator_feed
|
|
@ -0,0 +1,27 @@
|
|||
id: d6_aggregator_settings
|
||||
label: Aggregator configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Configuration
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- aggregator_fetcher
|
||||
- aggregator_parser
|
||||
- aggregator_processors
|
||||
- aggregator_allowed_html_tags
|
||||
- aggregator_teaser_length
|
||||
- aggregator_clear
|
||||
- aggregator_summary_items
|
||||
source_module: aggregator
|
||||
process:
|
||||
fetcher: aggregator_fetcher
|
||||
parser: aggregator_parser
|
||||
processors: aggregator_processors
|
||||
'items/allowed_html': aggregator_allowed_html_tags
|
||||
'items/teaser_length': aggregator_teaser_length
|
||||
'items/expire': aggregator_clear
|
||||
'source/list_max': aggregator_summary_items
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: aggregator.settings
|
|
@ -0,0 +1,22 @@
|
|||
id: d7_aggregator_feed
|
||||
label: Aggregator feeds
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
source:
|
||||
plugin: aggregator_feed
|
||||
process:
|
||||
fid: fid
|
||||
title: title
|
||||
url: url
|
||||
refresh: refresh
|
||||
checked: checked
|
||||
queued: queued
|
||||
link: link
|
||||
description: description
|
||||
image: image
|
||||
etag: etag
|
||||
modified: modified
|
||||
destination:
|
||||
plugin: entity:aggregator_feed
|
|
@ -0,0 +1,25 @@
|
|||
id: d7_aggregator_item
|
||||
label: Aggregator items
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
source:
|
||||
plugin: aggregator_item
|
||||
process:
|
||||
iid: iid
|
||||
fid:
|
||||
plugin: migration_lookup
|
||||
migration: d7_aggregator_feed
|
||||
source: fid
|
||||
title: title
|
||||
link: link
|
||||
author: author
|
||||
description: description
|
||||
timestamp: timestamp
|
||||
guid: guid
|
||||
destination:
|
||||
plugin: entity:aggregator_item
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_aggregator_feed
|
|
@ -0,0 +1,27 @@
|
|||
id: d7_aggregator_settings
|
||||
label: Aggregator configuration
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: variable
|
||||
variables:
|
||||
- aggregator_fetcher
|
||||
- aggregator_parser
|
||||
- aggregator_processors
|
||||
- aggregator_allowed_html_tags
|
||||
- aggregator_teaser_length
|
||||
- aggregator_clear
|
||||
- aggregator_summary_items
|
||||
source_module: aggregator
|
||||
process:
|
||||
fetcher: aggregator_fetcher
|
||||
parser: aggregator_parser
|
||||
processors: aggregator_processors
|
||||
'items/allowed_html': aggregator_allowed_html_tags
|
||||
'items/teaser_length': aggregator_teaser_length
|
||||
'items/expire': aggregator_clear
|
||||
'source/list_max': aggregator_summary_items
|
||||
destination:
|
||||
plugin: config
|
||||
config_name: aggregator.settings
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\views\EntityViewsData;
|
||||
|
||||
/**
|
||||
* Provides the views data for the aggregator feed entity type.
|
||||
*/
|
||||
class AggregatorFeedViewsData extends EntityViewsData {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewsData() {
|
||||
$data = parent::getViewsData();
|
||||
|
||||
$data['aggregator_feed']['table']['join'] = [
|
||||
'aggregator_item' => [
|
||||
'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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\views\EntityViewsData;
|
||||
|
||||
/**
|
||||
* Provides the views data for the aggregator item entity type.
|
||||
*/
|
||||
class AggregatorItemViewsData extends EntityViewsData {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewsData() {
|
||||
$data = parent::getViewsData();
|
||||
|
||||
$data['aggregator_item']['table']['base']['help'] = $this->t('Aggregator items are imported from external RSS and Atom news feeds.');
|
||||
|
||||
$data['aggregator_item']['iid']['help'] = $this->t('The unique ID of the aggregator item.');
|
||||
$data['aggregator_item']['iid']['argument']['id'] = 'aggregator_iid';
|
||||
$data['aggregator_item']['iid']['argument']['name field'] = 'title';
|
||||
$data['aggregator_item']['iid']['argument']['numeric'] = TRUE;
|
||||
|
||||
$data['aggregator_item']['title']['help'] = $this->t('The title of the aggregator item.');
|
||||
$data['aggregator_item']['title']['field']['default_formatter'] = 'aggregator_title';
|
||||
|
||||
$data['aggregator_item']['link']['help'] = $this->t('The link to the original source URL of the item.');
|
||||
|
||||
$data['aggregator_item']['author']['help'] = $this->t('The author of the original imported item.');
|
||||
|
||||
$data['aggregator_item']['author']['field']['default_formatter'] = 'aggregator_xss';
|
||||
|
||||
$data['aggregator_item']['guid']['help'] = $this->t('The guid of the original imported item.');
|
||||
|
||||
$data['aggregator_item']['description']['help'] = $this->t('The actual content of the imported item.');
|
||||
$data['aggregator_item']['description']['field']['default_formatter'] = 'aggregator_xss';
|
||||
$data['aggregator_item']['description']['field']['click sortable'] = FALSE;
|
||||
|
||||
$data['aggregator_item']['timestamp']['help'] = $this->t('The date the original feed item was posted. (With some feeds, this will be the date it was imported.)');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Plugin annotation object for aggregator fetcher plugins.
|
||||
*
|
||||
* Plugin Namespace: Plugin\aggregator\fetcher
|
||||
*
|
||||
* For a working example, see \Drupal\aggregator\Plugin\aggregator\fetcher\DefaultFetcher
|
||||
*
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see \Drupal\aggregator\Plugin\FetcherInterface
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class AggregatorFetcher extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The title of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The description of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $description;
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Plugin annotation object for aggregator parser plugins.
|
||||
*
|
||||
* Plugin Namespace: Plugin\aggregator\parser
|
||||
*
|
||||
* For a working example, see \Drupal\aggregator\Plugin\aggregator\parser\DefaultParser
|
||||
*
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see \Drupal\aggregator\Plugin\ParserInterface
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class AggregatorParser extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The title of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The description of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $description;
|
||||
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\Plugin;
|
||||
|
||||
/**
|
||||
* Defines a Plugin annotation object for aggregator processor plugins.
|
||||
*
|
||||
* Plugin Namespace: Plugin\aggregator\processor
|
||||
*
|
||||
* For a working example, see \Drupal\aggregator\Plugin\aggregator\processor\DefaultProcessor
|
||||
*
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see \Drupal\aggregator\Plugin\ProcessorInterface
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class AggregatorProcessor extends Plugin {
|
||||
|
||||
/**
|
||||
* The plugin ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* The title of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $title;
|
||||
|
||||
/**
|
||||
* The description of the plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Annotation\Translation
|
||||
*
|
||||
* @ingroup plugin_translatable
|
||||
*/
|
||||
public $description;
|
||||
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Controller;
|
||||
|
||||
use Drupal\Component\Utility\Xss;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Returns responses for aggregator module routes.
|
||||
*/
|
||||
class AggregatorController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The date formatter service.
|
||||
*
|
||||
* @var \Drupal\Core\Datetime\DateFormatterInterface
|
||||
*/
|
||||
protected $dateFormatter;
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\aggregator\Controller\AggregatorController object.
|
||||
*
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
*/
|
||||
public function __construct(DateFormatterInterface $date_formatter) {
|
||||
$this->dateFormatter = $date_formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('date.formatter')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the aggregator feed creation form.
|
||||
*
|
||||
* @return array
|
||||
* A form array as expected by
|
||||
* \Drupal\Core\Render\RendererInterface::render().
|
||||
*/
|
||||
public function feedAdd() {
|
||||
$feed = $this->entityManager()->getStorage('aggregator_feed')->create();
|
||||
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 = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['class' => ['aggregator-wrapper']],
|
||||
];
|
||||
$build['feed_source'] = is_array($feed_source) ? $feed_source : ['#markup' => $feed_source];
|
||||
if ($items) {
|
||||
$build['items'] = $this->entityManager()->getViewBuilder('aggregator_item')
|
||||
->viewMultiple($items, 'default');
|
||||
$build['pager'] = ['#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.', ['%site' => $aggregator_feed->label()])
|
||||
: $this->t('There is no new syndicated content from %site.', ['%site' => $aggregator_feed->label()]);
|
||||
$this->messenger()->addStatus($message);
|
||||
return $this->redirect('aggregator.admin_overview');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the aggregator administration page.
|
||||
*
|
||||
* @return array
|
||||
* A render array as expected by
|
||||
* \Drupal\Core\Render\RendererInterface::render().
|
||||
*/
|
||||
public function adminOverview() {
|
||||
$entity_manager = $this->entityManager();
|
||||
$feeds = $entity_manager->getStorage('aggregator_feed')
|
||||
->loadMultiple();
|
||||
|
||||
$header = [$this->t('Title'), $this->t('Items'), $this->t('Last update'), $this->t('Next update'), $this->t('Operations')];
|
||||
$rows = [];
|
||||
/** @var \Drupal\aggregator\FeedInterface[] $feeds */
|
||||
foreach ($feeds as $feed) {
|
||||
$row = [];
|
||||
$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', ['@time' => $this->dateFormatter->formatInterval(REQUEST_TIME - $last_checked)]) : $this->t('never'));
|
||||
if (!$last_checked && $refresh_rate) {
|
||||
$next_update = $this->t('imminently');
|
||||
}
|
||||
elseif ($last_checked && $refresh_rate) {
|
||||
$next_update = $next = $this->t('%time left', ['%time' => $this->dateFormatter->formatInterval($last_checked + $refresh_rate - REQUEST_TIME)]);
|
||||
}
|
||||
else {
|
||||
$next_update = $this->t('never');
|
||||
}
|
||||
$row[] = $next_update;
|
||||
$links['edit'] = [
|
||||
'title' => $this->t('Edit'),
|
||||
'url' => Url::fromRoute('entity.aggregator_feed.edit_form', ['aggregator_feed' => $feed->id()]),
|
||||
];
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => Url::fromRoute('entity.aggregator_feed.delete_form', ['aggregator_feed' => $feed->id()]),
|
||||
];
|
||||
$links['delete_items'] = [
|
||||
'title' => $this->t('Delete items'),
|
||||
'url' => Url::fromRoute('aggregator.feed_items_delete', ['aggregator_feed' => $feed->id()]),
|
||||
];
|
||||
$links['update'] = [
|
||||
'title' => $this->t('Update items'),
|
||||
'url' => Url::fromRoute('aggregator.feed_refresh', ['aggregator_feed' => $feed->id()]),
|
||||
];
|
||||
$row[] = [
|
||||
'data' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
],
|
||||
];
|
||||
$rows[] = $row;
|
||||
}
|
||||
$build['feeds'] = [
|
||||
'#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>.', [':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'][] = ['aggregator/rss', $this->config('system.site')->get('name') . ' ' . $this->t('aggregator')];
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route title callback.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $aggregator_feed
|
||||
* The aggregator feed.
|
||||
*
|
||||
* @return array
|
||||
* The feed label as a render array.
|
||||
*/
|
||||
public function feedTitle(FeedInterface $aggregator_feed) {
|
||||
return ['#markup' => $aggregator_feed->label(), '#allowed_tags' => Xss::getHtmlTagList()];
|
||||
}
|
||||
|
||||
}
|
397
2017/web/core/modules/aggregator/src/Entity/Feed.php
Normal file
397
2017/web/core/modules/aggregator/src/Entity/Feed.php
Normal file
|
@ -0,0 +1,397 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Entity;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines the aggregator feed entity class.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "aggregator_feed",
|
||||
* label = @Translation("Aggregator feed"),
|
||||
* label_collection = @Translation("Aggregator feeds"),
|
||||
* label_singular = @Translation("aggregator feed"),
|
||||
* label_plural = @Translation("aggregator feeds"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count aggregator feed",
|
||||
* plural = "@count aggregator feeds",
|
||||
* ),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\aggregator\FeedStorage",
|
||||
* "storage_schema" = "Drupal\aggregator\FeedStorageSchema",
|
||||
* "view_builder" = "Drupal\aggregator\FeedViewBuilder",
|
||||
* "access" = "Drupal\aggregator\FeedAccessControlHandler",
|
||||
* "views_data" = "Drupal\aggregator\AggregatorFeedViewsData",
|
||||
* "form" = {
|
||||
* "default" = "Drupal\aggregator\FeedForm",
|
||||
* "delete" = "Drupal\aggregator\Form\FeedDeleteForm",
|
||||
* "delete_items" = "Drupal\aggregator\Form\FeedItemsDeleteForm",
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\aggregator\FeedHtmlRouteProvider",
|
||||
* },
|
||||
* },
|
||||
* links = {
|
||||
* "canonical" = "/aggregator/sources/{aggregator_feed}",
|
||||
* "edit-form" = "/aggregator/sources/{aggregator_feed}/configure",
|
||||
* "delete-form" = "/aggregator/sources/{aggregator_feed}/delete",
|
||||
* },
|
||||
* field_ui_base_route = "aggregator.admin_overview",
|
||||
* base_table = "aggregator_feed",
|
||||
* render_cache = FALSE,
|
||||
* entity_keys = {
|
||||
* "id" = "fid",
|
||||
* "label" = "title",
|
||||
* "langcode" = "langcode",
|
||||
* "uuid" = "uuid",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Feed extends ContentEntityBase implements FeedInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->get('title')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteItems() {
|
||||
\Drupal::service('aggregator.items.importer')->delete($this);
|
||||
|
||||
// Reset feed.
|
||||
$this->setLastCheckedTime(0);
|
||||
$this->setHash('');
|
||||
$this->setEtag('');
|
||||
$this->setLastModified(0);
|
||||
$this->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function refreshItems() {
|
||||
$success = \Drupal::service('aggregator.items.importer')->refresh($this);
|
||||
|
||||
// Regardless of successful or not, indicate that it has been checked.
|
||||
$this->setLastCheckedTime(REQUEST_TIME);
|
||||
$this->setQueuedTime(0);
|
||||
$this->save();
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function preCreate(EntityStorageInterface $storage, array &$values) {
|
||||
$values += [
|
||||
'link' => '',
|
||||
'description' => '',
|
||||
'image' => '',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function preDelete(EntityStorageInterface $storage, array $entities) {
|
||||
foreach ($entities as $entity) {
|
||||
// Notify processors to delete stored items.
|
||||
\Drupal::service('aggregator.items.importer')->delete($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::postDelete($storage, $entities);
|
||||
if (\Drupal::moduleHandler()->moduleExists('block')) {
|
||||
// Make sure there are no active blocks for these feeds.
|
||||
$ids = \Drupal::entityQuery('block')
|
||||
->condition('plugin', 'aggregator_feed_block')
|
||||
->condition('settings.feed', array_keys($entities))
|
||||
->execute();
|
||||
if ($ids) {
|
||||
$block_storage = \Drupal::entityManager()->getStorage('block');
|
||||
$block_storage->delete($block_storage->loadMultiple($ids));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['fid']->setLabel(t('Feed ID'))
|
||||
->setDescription(t('The ID of the aggregator feed.'));
|
||||
|
||||
$fields['uuid']->setDescription(t('The aggregator feed UUID.'));
|
||||
|
||||
$fields['langcode']->setLabel(t('Language code'))
|
||||
->setDescription(t('The feed language code.'));
|
||||
|
||||
$fields['title'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Title'))
|
||||
->setDescription(t('The name of the feed (or the name of the website providing the feed).'))
|
||||
->setRequired(TRUE)
|
||||
->setSetting('max_length', 255)
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'string_textfield',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->addConstraint('FeedTitle');
|
||||
|
||||
$fields['url'] = BaseFieldDefinition::create('uri')
|
||||
->setLabel(t('URL'))
|
||||
->setDescription(t('The fully-qualified URL of the feed.'))
|
||||
->setRequired(TRUE)
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'uri',
|
||||
'weight' => -3,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->addConstraint('FeedUrl');
|
||||
|
||||
$intervals = [900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200];
|
||||
$period = array_map([\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.'))
|
||||
->setDefaultValue(3600)
|
||||
->setSetting('unsigned', TRUE)
|
||||
->setRequired(TRUE)
|
||||
->setSetting('allowed_values', $period)
|
||||
->setDisplayOptions('form', [
|
||||
'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', [
|
||||
'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', [
|
||||
'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.", ['@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;
|
||||
}
|
||||
|
||||
}
|
249
2017/web/core/modules/aggregator/src/Entity/Item.php
Normal file
249
2017/web/core/modules/aggregator/src/Entity/Item.php
Normal file
|
@ -0,0 +1,249 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Entity;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\ContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\aggregator\ItemInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Defines the aggregator item entity class.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "aggregator_item",
|
||||
* label = @Translation("Aggregator feed item"),
|
||||
* label_collection = @Translation("Aggregator feed items"),
|
||||
* label_singular = @Translation("aggregator feed item"),
|
||||
* label_plural = @Translation("aggregator feed items"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count aggregator feed item",
|
||||
* plural = "@count aggregator feed items",
|
||||
* ),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\aggregator\ItemStorage",
|
||||
* "storage_schema" = "Drupal\aggregator\ItemStorageSchema",
|
||||
* "view_builder" = "Drupal\aggregator\ItemViewBuilder",
|
||||
* "access" = "Drupal\aggregator\FeedAccessControlHandler",
|
||||
* "views_data" = "Drupal\aggregator\AggregatorItemViewsData"
|
||||
* },
|
||||
* uri_callback = "Drupal\aggregator\Entity\Item::buildUri",
|
||||
* base_table = "aggregator_item",
|
||||
* render_cache = FALSE,
|
||||
* list_cache_tags = { "aggregator_feed_list" },
|
||||
* entity_keys = {
|
||||
* "id" = "iid",
|
||||
* "label" = "title",
|
||||
* "langcode" = "langcode",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class Item extends ContentEntityBase implements ItemInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function label() {
|
||||
return $this->get('title')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['iid']->setLabel(t('Aggregator item ID'))
|
||||
->setDescription(t('The ID of the feed item.'));
|
||||
|
||||
$fields['langcode']->setLabel(t('Language code'))
|
||||
->setDescription(t('The feed item language code.'));
|
||||
|
||||
$fields['fid'] = BaseFieldDefinition::create('entity_reference')
|
||||
->setLabel(t('Source feed'))
|
||||
->setRequired(TRUE)
|
||||
->setDescription(t('The aggregator feed entity associated with this item.'))
|
||||
->setSetting('target_type', 'aggregator_feed')
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'entity_reference_label',
|
||||
'weight' => 0,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['title'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Title'))
|
||||
->setDescription(t('The title of the feed item.'));
|
||||
|
||||
$fields['link'] = BaseFieldDefinition::create('uri')
|
||||
->setLabel(t('Link'))
|
||||
->setDescription(t('The link of the feed item.'))
|
||||
->setDisplayOptions('view', [
|
||||
'region' => 'hidden',
|
||||
])
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
$fields['author'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Author'))
|
||||
->setDescription(t('The author of the feed item.'))
|
||||
->setDisplayOptions('view', [
|
||||
'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', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'timestamp_ago',
|
||||
'weight' => 1,
|
||||
])
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
// @todo Convert to a real UUID field in
|
||||
// https://www.drupal.org/node/2149851.
|
||||
$fields['guid'] = BaseFieldDefinition::create('string_long')
|
||||
->setLabel(t('GUID'))
|
||||
->setDescription(t('Unique identifier for the feed item.'));
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFeedId() {
|
||||
return $this->get('fid')->target_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFeedId($fid) {
|
||||
return $this->set('fid', $fid);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTitle() {
|
||||
return $this->get('title')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTitle($title) {
|
||||
return $this->set('title', $title);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLink() {
|
||||
return $this->get('link')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLink($link) {
|
||||
return $this->set('link', $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAuthor() {
|
||||
return $this->get('author')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAuthor($author) {
|
||||
return $this->set('author', $author);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->get('description')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setDescription($description) {
|
||||
return $this->set('description', $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getPostedTime() {
|
||||
return $this->get('timestamp')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPostedTime($timestamp) {
|
||||
return $this->set('timestamp', $timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getGuid() {
|
||||
return $this->get('guid')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setGuid($guid) {
|
||||
return $this->set('guid', $guid);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||
parent::postSave($storage, $update);
|
||||
|
||||
// Entity::postSave() calls Entity::invalidateTagsOnSave(), which only
|
||||
// handles the regular cases. The Item entity has one special case: a newly
|
||||
// created Item is *also* associated with a Feed, so we must invalidate the
|
||||
// associated Feed's cache tag.
|
||||
if (!$update) {
|
||||
Cache::invalidateTags($this->getCacheTagsToInvalidate());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTagsToInvalidate() {
|
||||
return Feed::load($this->getFeedId())->getCacheTags();
|
||||
}
|
||||
|
||||
/**
|
||||
* Entity URI callback.
|
||||
*/
|
||||
public static function buildUri(ItemInterface $item) {
|
||||
return Url::fromUri($item->getLink());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines an access control handler for the feed entity.
|
||||
*
|
||||
* @see \Drupal\aggregator\Entity\Feed
|
||||
*/
|
||||
class FeedAccessControlHandler extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
switch ($operation) {
|
||||
case 'view':
|
||||
return AccessResult::allowedIfHasPermission($account, 'access news feeds');
|
||||
|
||||
default:
|
||||
return AccessResult::allowedIfHasPermission($account, 'administer news feeds');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
return AccessResult::allowedIfHasPermission($account, 'administer news feeds');
|
||||
}
|
||||
|
||||
}
|
34
2017/web/core/modules/aggregator/src/FeedForm.php
Normal file
34
2017/web/core/modules/aggregator/src/FeedForm.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Form handler for the aggregator feed edit forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FeedForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$feed = $this->entity;
|
||||
$status = $feed->save();
|
||||
$label = $feed->label();
|
||||
$view_link = $feed->link($label, 'canonical');
|
||||
if ($status == SAVED_UPDATED) {
|
||||
$this->messenger()->addStatus($this->t('The feed %feed has been updated.', ['%feed' => $view_link]));
|
||||
$form_state->setRedirectUrl($feed->urlInfo('canonical'));
|
||||
}
|
||||
else {
|
||||
$this->logger('aggregator')->notice('Feed %feed added.', ['%feed' => $feed->label(), 'link' => $this->l($this->t('View'), new Url('aggregator.admin_overview'))]);
|
||||
$this->messenger()->addStatus($this->t('The feed %feed has been added.', ['%feed' => $view_link]));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
|
||||
|
||||
/**
|
||||
* Provides HTML routes for the feed entity type.
|
||||
*/
|
||||
class FeedHtmlRouteProvider extends AdminHtmlRouteProvider {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
|
||||
$route = parent::getCanonicalRoute($entity_type);
|
||||
$route->setDefault('_title_controller', '\Drupal\aggregator\Controller\AggregatorController::feedTitle');
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEditFormRoute(EntityTypeInterface $entity_type) {
|
||||
$route = parent::getEditFormRoute($entity_type);
|
||||
|
||||
$route->setDefault('_title', 'Configure');
|
||||
|
||||
return $route;
|
||||
}
|
||||
|
||||
}
|
239
2017/web/core/modules/aggregator/src/FeedInterface.php
Normal file
239
2017/web/core/modules/aggregator/src/FeedInterface.php
Normal file
|
@ -0,0 +1,239 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining an aggregator feed entity.
|
||||
*/
|
||||
interface FeedInterface extends ContentEntityInterface {
|
||||
|
||||
/**
|
||||
* Sets the title of the feed.
|
||||
*
|
||||
* @param string $title
|
||||
* The short title of the feed.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setTitle($title);
|
||||
|
||||
/**
|
||||
* Returns the url to the feed.
|
||||
*
|
||||
* @return string
|
||||
* The url to the feed.
|
||||
*/
|
||||
public function getUrl();
|
||||
|
||||
/**
|
||||
* Sets the url to the feed.
|
||||
*
|
||||
* @param string $url
|
||||
* A string containing the url of the feed.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setUrl($url);
|
||||
|
||||
/**
|
||||
* Returns the refresh rate of the feed in seconds.
|
||||
*
|
||||
* @return int
|
||||
* The refresh rate of the feed in seconds.
|
||||
*/
|
||||
public function getRefreshRate();
|
||||
|
||||
/**
|
||||
* Sets the refresh rate of the feed in seconds.
|
||||
*
|
||||
* @param int $refresh
|
||||
* The refresh rate of the feed in seconds.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setRefreshRate($refresh);
|
||||
|
||||
/**
|
||||
* Returns the last time where the feed was checked for new items.
|
||||
*
|
||||
* @return int
|
||||
* The timestamp when new items were last checked for.
|
||||
*/
|
||||
public function getLastCheckedTime();
|
||||
|
||||
/**
|
||||
* Sets the time when this feed was queued for refresh, 0 if not queued.
|
||||
*
|
||||
* @param int $checked
|
||||
* The timestamp of the last refresh.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setLastCheckedTime($checked);
|
||||
|
||||
/**
|
||||
* Returns the time when this feed was queued for refresh, 0 if not queued.
|
||||
*
|
||||
* @return int
|
||||
* The timestamp of the last refresh.
|
||||
*/
|
||||
public function getQueuedTime();
|
||||
|
||||
/**
|
||||
* Sets the time when this feed was queued for refresh, 0 if not queued.
|
||||
*
|
||||
* @param int $queued
|
||||
* The timestamp of the last refresh.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setQueuedTime($queued);
|
||||
|
||||
/**
|
||||
* Returns the parent website of the feed.
|
||||
*
|
||||
* @return string
|
||||
* The parent website of the feed.
|
||||
*/
|
||||
public function getWebsiteUrl();
|
||||
|
||||
/**
|
||||
* Sets the parent website of the feed.
|
||||
*
|
||||
* @param string $link
|
||||
* A string containing the parent website of the feed.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setWebsiteUrl($link);
|
||||
|
||||
/**
|
||||
* Returns the description of the feed.
|
||||
*
|
||||
* @return string
|
||||
* The description of the feed.
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Sets the description of the feed.
|
||||
*
|
||||
* @param string $description
|
||||
* The description of the feed.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setDescription($description);
|
||||
|
||||
/**
|
||||
* Returns the primary image attached to the feed.
|
||||
*
|
||||
* @return string
|
||||
* The URL of the primary image attached to the feed.
|
||||
*/
|
||||
public function getImage();
|
||||
|
||||
/**
|
||||
* Sets the primary image attached to the feed.
|
||||
*
|
||||
* @param string $image
|
||||
* An image URL.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setImage($image);
|
||||
|
||||
/**
|
||||
* Returns the calculated hash of the feed data, used for validating cache.
|
||||
*
|
||||
* @return string
|
||||
* The calculated hash of the feed data.
|
||||
*/
|
||||
public function getHash();
|
||||
|
||||
/**
|
||||
* Sets the calculated hash of the feed data, used for validating cache.
|
||||
*
|
||||
* @param string $hash
|
||||
* A string containing the calculated hash of the feed. Must contain
|
||||
* US ASCII characters only.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setHash($hash);
|
||||
|
||||
/**
|
||||
* Returns the entity tag HTTP response header, used for validating cache.
|
||||
*
|
||||
* @return string
|
||||
* The entity tag HTTP response header.
|
||||
*/
|
||||
public function getEtag();
|
||||
|
||||
/**
|
||||
* Sets the entity tag HTTP response header, used for validating cache.
|
||||
*
|
||||
* @param string $etag
|
||||
* A string containing the entity tag HTTP response header.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setEtag($etag);
|
||||
|
||||
/**
|
||||
* Return when the feed was modified last time.
|
||||
*
|
||||
* @return int
|
||||
* The timestamp of the last time the feed was modified.
|
||||
*/
|
||||
public function getLastModified();
|
||||
|
||||
/**
|
||||
* Sets the last modification of the feed.
|
||||
*
|
||||
* @param int $modified
|
||||
* The timestamp when the feed was modified.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setLastModified($modified);
|
||||
|
||||
/**
|
||||
* Deletes all items from a feed.
|
||||
*
|
||||
* This will also reset the last checked and modified time of the feed and
|
||||
* save it.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* The class instance that this method is called on.
|
||||
*
|
||||
* @see \Drupal\aggregator\ItemsImporterInterface::delete()
|
||||
*/
|
||||
public function deleteItems();
|
||||
|
||||
/**
|
||||
* Updates the feed items by triggering the import process.
|
||||
*
|
||||
* This will also update the last checked time of the feed and save it.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there is new content for the feed FALSE otherwise.
|
||||
*
|
||||
* @see \Drupal\aggregator\ItemsImporterInterface::refresh()
|
||||
*/
|
||||
public function refreshItems();
|
||||
|
||||
}
|
25
2017/web/core/modules/aggregator/src/FeedStorage.php
Normal file
25
2017/web/core/modules/aggregator/src/FeedStorage.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
|
||||
/**
|
||||
* Controller class for aggregator's feeds.
|
||||
*
|
||||
* This extends the Drupal\Core\Entity\Sql\SqlContentEntityStorage class, adding
|
||||
* required special handling for feed entities.
|
||||
*/
|
||||
class FeedStorage extends SqlContentEntityStorage implements FeedStorageInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFeedIdsToRefresh() {
|
||||
return $this->database->query('SELECT fid FROM {' . $this->getBaseTable() . '} WHERE queued = 0 AND checked + refresh < :time AND refresh <> :never', [
|
||||
':time' => REQUEST_TIME,
|
||||
':never' => AGGREGATOR_CLEAR_NEVER,
|
||||
])->fetchCol();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator feed entity storage classes.
|
||||
*/
|
||||
interface FeedStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Denotes that a feed's items should never expire.
|
||||
*/
|
||||
const CLEAR_NEVER = 0;
|
||||
|
||||
/**
|
||||
* Returns the fids of feeds that need to be refreshed.
|
||||
*
|
||||
* @return array
|
||||
* A list of feed ids to be refreshed.
|
||||
*/
|
||||
public function getFeedIdsToRefresh();
|
||||
|
||||
}
|
36
2017/web/core/modules/aggregator/src/FeedStorageSchema.php
Normal file
36
2017/web/core/modules/aggregator/src/FeedStorageSchema.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Defines the feed schema handler.
|
||||
*/
|
||||
class FeedStorageSchema extends SqlContentEntityStorageSchema {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
|
||||
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
|
||||
$field_name = $storage_definition->getName();
|
||||
|
||||
if ($table_name == $this->storage->getBaseTable()) {
|
||||
switch ($field_name) {
|
||||
case 'url':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE, 255);
|
||||
break;
|
||||
|
||||
case 'queued':
|
||||
case 'title':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
135
2017/web/core/modules/aggregator/src/FeedViewBuilder.php
Normal file
135
2017/web/core/modules/aggregator/src/FeedViewBuilder.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
use Drupal\Core\Config\Config;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* View builder handler for aggregator feeds.
|
||||
*/
|
||||
class FeedViewBuilder extends EntityViewBuilder {
|
||||
|
||||
/**
|
||||
* Constructs a new FeedViewBuilder.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type definition.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Config\Config $config
|
||||
* The 'aggregator.settings' config.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, Config $config) {
|
||||
parent::__construct($entity_type, $entity_manager, $language_manager);
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('entity.manager'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('config.factory')->get('aggregator.settings')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildComponents(array &$build, array $entities, array $displays, $view_mode) {
|
||||
parent::buildComponents($build, $entities, $displays, $view_mode);
|
||||
|
||||
foreach ($entities as $id => $entity) {
|
||||
$bundle = $entity->bundle();
|
||||
$display = $displays[$bundle];
|
||||
|
||||
if ($display->getComponent('items')) {
|
||||
// When in summary view mode, respect the list_max setting.
|
||||
$limit = $view_mode == 'summary' ? $this->config->get('source.list_max') : 20;
|
||||
// Retrieve the items attached to this feed.
|
||||
$items = $this->entityManager
|
||||
->getStorage('aggregator_item')
|
||||
->loadByFeed($entity->id(), $limit);
|
||||
|
||||
$build[$id]['items'] = $this->entityManager
|
||||
->getViewBuilder('aggregator_item')
|
||||
->viewMultiple($items, $view_mode, $entity->language()->getId());
|
||||
|
||||
if ($view_mode == 'full') {
|
||||
// Also add the pager.
|
||||
$build[$id]['pager'] = ['#type' => 'pager'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($display->getComponent('description')) {
|
||||
$build[$id]['description'] = [
|
||||
'#markup' => $entity->getDescription(),
|
||||
'#allowed_tags' => _aggregator_allowed_tags(),
|
||||
'#prefix' => '<div class="feed-description">',
|
||||
'#suffix' => '</div>',
|
||||
];
|
||||
}
|
||||
|
||||
if ($display->getComponent('image')) {
|
||||
$image_link = [];
|
||||
// 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 = [
|
||||
'#theme' => 'image',
|
||||
'#uri' => $image,
|
||||
'#alt' => $label,
|
||||
];
|
||||
$image_link = [
|
||||
'#type' => 'link',
|
||||
'#title' => $link_title,
|
||||
'#url' => Url::fromUri($link_href),
|
||||
'#options' => [
|
||||
'attributes' => ['class' => ['feed-image']],
|
||||
],
|
||||
];
|
||||
}
|
||||
$build[$id]['image'] = $image_link;
|
||||
}
|
||||
|
||||
if ($display->getComponent('feed_icon')) {
|
||||
$build[$id]['feed_icon'] = [
|
||||
'#theme' => 'feed_icon',
|
||||
'#url' => $entity->getUrl(),
|
||||
'#title' => t('@title feed', ['@title' => $entity->label()]),
|
||||
];
|
||||
}
|
||||
|
||||
if ($display->getComponent('more_link')) {
|
||||
$title_stripped = strip_tags($entity->label());
|
||||
$build[$id]['more_link'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => t('More<span class="visually-hidden"> posts about @title</span>', [
|
||||
'@title' => $title_stripped,
|
||||
]),
|
||||
'#url' => Url::fromRoute('entity.aggregator_feed.canonical', ['aggregator_feed' => $entity->id()]),
|
||||
'#options' => [
|
||||
'attributes' => [
|
||||
'title' => $title_stripped,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
38
2017/web/core/modules/aggregator/src/Form/FeedDeleteForm.php
Normal file
38
2017/web/core/modules/aggregator/src/Form/FeedDeleteForm.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityDeleteForm;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a form for deleting a feed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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.', [
|
||||
'%label' => $this->entity->label(),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityConfirmFormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides a deletion confirmation form for items that belong to a feed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class FeedItemsDeleteForm extends ContentEntityConfirmFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete all items from the feed %feed?', ['%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());
|
||||
}
|
||||
|
||||
}
|
212
2017/web/core/modules/aggregator/src/Form/OpmlFeedAdd.php
Normal file
212
2017/web/core/modules/aggregator/src/Form/OpmlFeedAdd.php
Normal file
|
@ -0,0 +1,212 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Form;
|
||||
|
||||
use Drupal\aggregator\FeedStorageInterface;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\ClientInterface;
|
||||
|
||||
/**
|
||||
* Imports feeds from OPML.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
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 = [900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200];
|
||||
$period = array_map([\Drupal::service('date.formatter'), 'formatInterval'], array_combine($intervals, $intervals));
|
||||
|
||||
$form['upload'] = [
|
||||
'#type' => 'file',
|
||||
'#title' => $this->t('OPML File'),
|
||||
'#description' => $this->t('Upload an OPML file containing a list of feeds to be imported.'),
|
||||
];
|
||||
$form['remote'] = [
|
||||
'#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'] = [
|
||||
'#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>.', [':cron' => $this->url('system.status')]),
|
||||
];
|
||||
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Import'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
// If both fields are empty or filled, cancel.
|
||||
$all_files = $this->getRequest()->files->get('files', []);
|
||||
if ($form_state->isValueEmpty('remote') == empty($all_files['upload'])) {
|
||||
$form_state->setErrorByName('remote', $this->t('<em>Either</em> upload a file or enter a URL.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$validators = ['file_validate_extensions' => ['opml xml']];
|
||||
if ($file = file_save_upload('upload', $validators, FALSE, 0)) {
|
||||
$data = file_get_contents($file->getFileUri());
|
||||
}
|
||||
else {
|
||||
// @todo Move this to a fetcher implementation.
|
||||
try {
|
||||
$response = $this->httpClient->get($form_state->getValue('remote'));
|
||||
$data = (string) $response->getBody();
|
||||
}
|
||||
catch (RequestException $e) {
|
||||
$this->logger('aggregator')->warning('Failed to download OPML file due to "%error".', ['%error' => $e->getMessage()]);
|
||||
$this->messenger()->addStatus($this->t('Failed to download OPML file due to "%error".', ['%error' => $e->getMessage()]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$feeds = $this->parseOpml($data);
|
||||
if (empty($feeds)) {
|
||||
$this->messenger()->addStatus($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)) {
|
||||
$this->messenger()->addWarning($this->t('The URL %url is invalid.', ['%url' => $feed['url']]));
|
||||
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) {
|
||||
$this->messenger()->addWarning($this->t('A feed named %title already exists.', ['%title' => $old->label()]));
|
||||
continue 2;
|
||||
}
|
||||
if (strcasecmp($old->getUrl(), $feed['url']) == 0) {
|
||||
$this->messenger()->addWarning($this->t('A feed with the URL %url already exists.', ['%url' => $old->getUrl()]));
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$new_feed = $this->feedStorage->create([
|
||||
'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 = [];
|
||||
$xml_parser = xml_parser_create();
|
||||
xml_parser_set_option($xml_parser, XML_OPTION_TARGET_ENCODING, 'utf-8');
|
||||
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[] = ['title' => $item['TEXT'], 'url' => $item['XMLURL']];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
xml_parser_free($xml_parser);
|
||||
|
||||
return $feeds;
|
||||
}
|
||||
|
||||
}
|
227
2017/web/core/modules/aggregator/src/Form/SettingsForm.php
Normal file
227
2017/web/core/modules/aggregator/src/Form/SettingsForm.php
Normal file
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Form;
|
||||
|
||||
use Drupal\aggregator\Plugin\AggregatorPluginManager;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
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.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SettingsForm extends ConfigFormBase {
|
||||
|
||||
/**
|
||||
* The aggregator plugin managers.
|
||||
*
|
||||
* @var \Drupal\aggregator\Plugin\AggregatorPluginManager[]
|
||||
*/
|
||||
protected $managers = [];
|
||||
|
||||
/**
|
||||
* The instantiated plugin instances that have configuration forms.
|
||||
*
|
||||
* @var \Drupal\Core\Plugin\PluginFormInterface[]
|
||||
*/
|
||||
protected $configurableInstances = [];
|
||||
|
||||
/**
|
||||
* The aggregator plugin definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $definitions = [
|
||||
'fetcher' => [],
|
||||
'parser' => [],
|
||||
'processor' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* 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 = [
|
||||
'fetcher' => $fetcher_manager,
|
||||
'parser' => $parser_manager,
|
||||
'processor' => $processor_manager,
|
||||
];
|
||||
// Get all available fetcher, parser and processor definitions.
|
||||
foreach (['fetcher', 'parser', 'processor'] as $type) {
|
||||
foreach ($this->managers[$type]->getDefinitions() as $id => $definition) {
|
||||
$this->definitions[$type][$id] = new FormattableMarkup('@title <span class="description">@description</span>', ['@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'] = [
|
||||
'#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 = [];
|
||||
if (count($this->definitions['fetcher']) > 1) {
|
||||
$basic_conf['aggregator_fetcher'] = [
|
||||
'#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'] = [
|
||||
'#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'] = [
|
||||
'#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'] = [
|
||||
'#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 (['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'] = [];
|
||||
// Call buildConfigurationForm() for each active processor.
|
||||
foreach ($this->definitions['processor'] as $id => $definition) {
|
||||
if (in_array($id, $config->get('processors'))) {
|
||||
$instance = $this->managers['processor']->createInstance($id);
|
||||
if ($instance instanceof PluginFormInterface) {
|
||||
$form = $instance->buildConfigurationForm($form, $form_state);
|
||||
// Store the instance for validate and submit handlers.
|
||||
// Keying by ID would bring conflicts, because two instances of a
|
||||
// different type could have the same ID.
|
||||
$this->configurableInstances[] = $instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::validateForm($form, $form_state);
|
||||
// Let active plugins validate their settings.
|
||||
foreach ($this->configurableInstances as $instance) {
|
||||
$instance->validateConfigurationForm($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
parent::submitForm($form, $form_state);
|
||||
$config = $this->config('aggregator.settings');
|
||||
// Let active plugins save their settings.
|
||||
foreach ($this->configurableInstances as $instance) {
|
||||
$instance->submitConfigurationForm($form, $form_state);
|
||||
}
|
||||
|
||||
$config->set('items.allowed_html', $form_state->getValue('aggregator_allowed_html_tags'));
|
||||
if ($form_state->hasValue('aggregator_fetcher')) {
|
||||
$config->set('fetcher', $form_state->getValue('aggregator_fetcher'));
|
||||
}
|
||||
if ($form_state->hasValue('aggregator_parser')) {
|
||||
$config->set('parser', $form_state->getValue('aggregator_parser'));
|
||||
}
|
||||
if ($form_state->hasValue('aggregator_processors')) {
|
||||
$config->set('processors', array_filter($form_state->getValue('aggregator_processors')));
|
||||
}
|
||||
$config->save();
|
||||
}
|
||||
|
||||
}
|
145
2017/web/core/modules/aggregator/src/ItemInterface.php
Normal file
145
2017/web/core/modules/aggregator/src/ItemInterface.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining an aggregator item entity.
|
||||
*/
|
||||
interface ItemInterface extends ContentEntityInterface {
|
||||
|
||||
/**
|
||||
* Returns the feed id of aggregator item.
|
||||
*
|
||||
* @return int
|
||||
* The feed id.
|
||||
*/
|
||||
public function getFeedId();
|
||||
|
||||
/**
|
||||
* Sets the feed id of aggregator item.
|
||||
*
|
||||
* @param int $fid
|
||||
* The feed id.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setFeedId($fid);
|
||||
|
||||
/**
|
||||
* Returns the title of the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The title of the feed item.
|
||||
*/
|
||||
public function getTitle();
|
||||
|
||||
/**
|
||||
* Sets the title of the feed item.
|
||||
*
|
||||
* @param string $title
|
||||
* The title of the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setTitle($title);
|
||||
|
||||
/**
|
||||
* Returns the link to the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The link to the feed item.
|
||||
*/
|
||||
public function getLink();
|
||||
|
||||
/**
|
||||
* Sets the link to the feed item.
|
||||
*
|
||||
* @param string $link
|
||||
* The link to the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setLink($link);
|
||||
|
||||
/**
|
||||
* Returns the author of the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The author of the feed item.
|
||||
*/
|
||||
public function getAuthor();
|
||||
|
||||
/**
|
||||
* Sets the author of the feed item.
|
||||
*
|
||||
* @param string $author
|
||||
* The author name of the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setAuthor($author);
|
||||
|
||||
/**
|
||||
* Returns the body of the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The body of the feed item.
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
/**
|
||||
* Sets the body of the feed item.
|
||||
*
|
||||
* @param string $description
|
||||
* The body of the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setDescription($description);
|
||||
|
||||
/**
|
||||
* Returns the posted date of the feed item, as a Unix timestamp.
|
||||
*
|
||||
* @return int
|
||||
* The posted date of the feed item, as a Unix timestamp.
|
||||
*/
|
||||
public function getPostedTime();
|
||||
|
||||
/**
|
||||
* Sets the posted date of the feed item, as a Unix timestamp.
|
||||
*
|
||||
* @param int $timestamp
|
||||
* The posted date of the feed item, as a Unix timestamp.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setPostedTime($timestamp);
|
||||
|
||||
/**
|
||||
* Returns the unique identifier for the feed item.
|
||||
*
|
||||
* @return string
|
||||
* The unique identifier for the feed item.
|
||||
*/
|
||||
public function getGuid();
|
||||
|
||||
/**
|
||||
* Sets the unique identifier for the feed item.
|
||||
*
|
||||
* @param string $guid
|
||||
* The unique identifier for the feed item.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface
|
||||
* The called feed item entity.
|
||||
*/
|
||||
public function setGuid($guid);
|
||||
|
||||
}
|
65
2017/web/core/modules/aggregator/src/ItemStorage.php
Normal file
65
2017/web/core/modules/aggregator/src/ItemStorage.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
|
||||
/**
|
||||
* Controller class for aggregators items.
|
||||
*
|
||||
* This extends the Drupal\Core\Entity\Sql\SqlContentEntityStorage class, adding
|
||||
* required special handling for feed item entities.
|
||||
*/
|
||||
class ItemStorage extends SqlContentEntityStorage implements ItemStorageInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getItemCount(FeedInterface $feed) {
|
||||
$query = \Drupal::entityQuery('aggregator_item')
|
||||
->condition('fid', $feed->id())
|
||||
->count();
|
||||
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadAll($limit = NULL) {
|
||||
$query = \Drupal::entityQuery('aggregator_item');
|
||||
return $this->executeFeedItemQuery($query, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadByFeed($fid, $limit = NULL) {
|
||||
$query = \Drupal::entityQuery('aggregator_item')
|
||||
->condition('fid', $fid);
|
||||
return $this->executeFeedItemQuery($query, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to execute an item query.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\Query\QueryInterface $query
|
||||
* The query to execute.
|
||||
* @param int $limit
|
||||
* (optional) The number of items to return.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface[]
|
||||
* An array of the feed items.
|
||||
*/
|
||||
protected function executeFeedItemQuery(QueryInterface $query, $limit) {
|
||||
$query->sort('timestamp', 'DESC')
|
||||
->sort('iid', 'DESC');
|
||||
if (!empty($limit)) {
|
||||
$query->pager($limit);
|
||||
}
|
||||
|
||||
return $this->loadMultiple($query->execute());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator item entity storage classes.
|
||||
*/
|
||||
interface ItemStorageInterface extends ContentEntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Returns the count of the items in a feed.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* The feed entity.
|
||||
*
|
||||
* @return int
|
||||
* The count of items associated with a feed.
|
||||
*/
|
||||
public function getItemCount(FeedInterface $feed);
|
||||
|
||||
/**
|
||||
* Loads feed items from all feeds.
|
||||
*
|
||||
* @param int $limit
|
||||
* (optional) The number of items to return. Defaults to unlimited.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface[]
|
||||
* An array of the feed items.
|
||||
*/
|
||||
public function loadAll($limit = NULL);
|
||||
|
||||
/**
|
||||
* Loads feed items filtered by a feed.
|
||||
*
|
||||
* @param int $fid
|
||||
* The feed ID to filter by.
|
||||
* @param int $limit
|
||||
* (optional) The number of items to return. Defaults to unlimited.
|
||||
*
|
||||
* @return \Drupal\aggregator\ItemInterface[]
|
||||
* An array of the feed items.
|
||||
*/
|
||||
public function loadByFeed($fid, $limit = NULL);
|
||||
|
||||
}
|
35
2017/web/core/modules/aggregator/src/ItemStorageSchema.php
Normal file
35
2017/web/core/modules/aggregator/src/ItemStorageSchema.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Defines the item schema handler.
|
||||
*/
|
||||
class ItemStorageSchema extends SqlContentEntityStorageSchema {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping) {
|
||||
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
|
||||
$field_name = $storage_definition->getName();
|
||||
|
||||
if ($table_name == $this->storage->getBaseTable()) {
|
||||
switch ($field_name) {
|
||||
case 'timestamp':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
|
||||
break;
|
||||
|
||||
case 'fid':
|
||||
$this->addSharedTableFieldForeignKey($storage_definition, $schema, 'aggregator_feed', 'fid');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
33
2017/web/core/modules/aggregator/src/ItemViewBuilder.php
Normal file
33
2017/web/core/modules/aggregator/src/ItemViewBuilder.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
|
||||
/**
|
||||
* View builder handler for aggregator feed items.
|
||||
*/
|
||||
class ItemViewBuilder extends EntityViewBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildComponents(array &$build, array $entities, array $displays, $view_mode) {
|
||||
parent::buildComponents($build, $entities, $displays, $view_mode);
|
||||
|
||||
foreach ($entities as $id => $entity) {
|
||||
$bundle = $entity->bundle();
|
||||
$display = $displays[$bundle];
|
||||
|
||||
if ($display->getComponent('description')) {
|
||||
$build[$id]['description'] = [
|
||||
'#markup' => $entity->getDescription(),
|
||||
'#allowed_tags' => _aggregator_allowed_tags(),
|
||||
'#prefix' => '<div class="item-description">',
|
||||
'#suffix' => '</div>',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
153
2017/web/core/modules/aggregator/src/ItemsImporter.php
Normal file
153
2017/web/core/modules/aggregator/src/ItemsImporter.php
Normal file
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
use Drupal\aggregator\Plugin\AggregatorPluginManager;
|
||||
use Drupal\Component\Plugin\Exception\PluginException;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Defines an importer of aggregator items.
|
||||
*/
|
||||
class ItemsImporter implements ItemsImporterInterface {
|
||||
|
||||
/**
|
||||
* The aggregator fetcher manager.
|
||||
*
|
||||
* @var \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
*/
|
||||
protected $fetcherManager;
|
||||
|
||||
/**
|
||||
* The aggregator processor manager.
|
||||
*
|
||||
* @var \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
*/
|
||||
protected $processorManager;
|
||||
|
||||
/**
|
||||
* The aggregator parser manager.
|
||||
*
|
||||
* @var \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
*/
|
||||
protected $parserManager;
|
||||
|
||||
/**
|
||||
* The aggregator.settings config object.
|
||||
*
|
||||
* @var \Drupal\Core\Config\Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* A logger instance.
|
||||
*
|
||||
* @var \Psr\Log\LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructs an Importer object.
|
||||
*
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The factory for configuration objects.
|
||||
* @param \Drupal\aggregator\Plugin\AggregatorPluginManager $fetcher_manager
|
||||
* The aggregator fetcher plugin manager.
|
||||
* @param \Drupal\aggregator\Plugin\AggregatorPluginManager $parser_manager
|
||||
* The aggregator parser plugin manager.
|
||||
* @param \Drupal\aggregator\Plugin\AggregatorPluginManager $processor_manager
|
||||
* The aggregator processor plugin manager.
|
||||
* @param \Psr\Log\LoggerInterface $logger
|
||||
* A logger instance.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config_factory, AggregatorPluginManager $fetcher_manager, AggregatorPluginManager $parser_manager, AggregatorPluginManager $processor_manager, LoggerInterface $logger) {
|
||||
$this->fetcherManager = $fetcher_manager;
|
||||
$this->processorManager = $processor_manager;
|
||||
$this->parserManager = $parser_manager;
|
||||
$this->config = $config_factory->get('aggregator.settings');
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function delete(FeedInterface $feed) {
|
||||
foreach ($this->processorManager->getDefinitions() as $id => $definition) {
|
||||
$this->processorManager->createInstance($id)->delete($feed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function refresh(FeedInterface $feed) {
|
||||
// Store feed URL to track changes.
|
||||
$feed_url = $feed->getUrl();
|
||||
|
||||
// Fetch the feed.
|
||||
try {
|
||||
$success = $this->fetcherManager->createInstance($this->config->get('fetcher'))->fetch($feed);
|
||||
}
|
||||
catch (PluginException $e) {
|
||||
$success = FALSE;
|
||||
watchdog_exception('aggregator', $e);
|
||||
}
|
||||
|
||||
// Store instances in an array so we don't have to instantiate new objects.
|
||||
$processor_instances = [];
|
||||
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.', ['%title' => $feed->label(), '%url' => $feed->getUrl()]);
|
||||
}
|
||||
|
||||
$this->logger->notice('There is new syndicated content from %site.', ['%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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
||||
/**
|
||||
* Provides an interface defining an aggregator items importer.
|
||||
*/
|
||||
interface ItemsImporterInterface {
|
||||
|
||||
/**
|
||||
* Updates the feed items by triggering the import process.
|
||||
*
|
||||
* This process can be slow and lengthy because it relies on network
|
||||
* operations. Calling it on performance critical paths should be avoided.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* The feed which items should be refreshed.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if there is new content for the feed FALSE otherwise.
|
||||
*/
|
||||
public function refresh(FeedInterface $feed);
|
||||
|
||||
/**
|
||||
* Deletes all imported items from a feed.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* The feed that associated items should be deleted from.
|
||||
*/
|
||||
public function delete(FeedInterface $feed);
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
|
||||
/**
|
||||
* Manages aggregator plugins.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorParser
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorFetcher
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorProcessor
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see \Drupal\aggregator\Plugin\FetcherInterface
|
||||
* @see \Drupal\aggregator\Plugin\ProcessorInterface
|
||||
* @see \Drupal\aggregator\Plugin\ParserInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
class AggregatorPluginManager extends DefaultPluginManager {
|
||||
|
||||
/**
|
||||
* Constructs a AggregatorPluginManager object.
|
||||
*
|
||||
* @param string $type
|
||||
* The plugin type, for example fetcher.
|
||||
* @param \Traversable $namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* Cache backend instance to use.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
$type_annotations = [
|
||||
'fetcher' => 'Drupal\aggregator\Annotation\AggregatorFetcher',
|
||||
'parser' => 'Drupal\aggregator\Annotation\AggregatorParser',
|
||||
'processor' => 'Drupal\aggregator\Annotation\AggregatorProcessor',
|
||||
];
|
||||
$plugin_interfaces = [
|
||||
'fetcher' => 'Drupal\aggregator\Plugin\FetcherInterface',
|
||||
'parser' => 'Drupal\aggregator\Plugin\ParserInterface',
|
||||
'processor' => 'Drupal\aggregator\Plugin\ProcessorInterface',
|
||||
];
|
||||
|
||||
parent::__construct("Plugin/aggregator/$type", $namespaces, $module_handler, $plugin_interfaces[$type], $type_annotations[$type]);
|
||||
$this->setCacheBackend($cache_backend, 'aggregator_' . $type . '_plugins');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\ConfigurablePluginInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\Core\Plugin\PluginFormInterface;
|
||||
|
||||
/**
|
||||
* Base class for aggregator plugins that implement settings forms.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorParser
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorFetcher
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorProcessor
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see \Drupal\aggregator\Plugin\FetcherInterface
|
||||
* @see \Drupal\aggregator\Plugin\ProcessorInterface
|
||||
* @see \Drupal\aggregator\Plugin\ParserInterface
|
||||
* @see plugin_api
|
||||
*/
|
||||
abstract class AggregatorPluginSettingsBase extends PluginBase implements PluginFormInterface, ConfigurablePluginInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Block;
|
||||
|
||||
use Drupal\aggregator\FeedStorageInterface;
|
||||
use Drupal\aggregator\ItemStorageInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, FeedStorageInterface $feed_storage, ItemStorageInterface $item_storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->feedStorage = $feed_storage;
|
||||
$this->itemStorage = $item_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity_type.manager')->getStorage('aggregator_feed'),
|
||||
$container->get('entity_type.manager')->getStorage('aggregator_item')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
// By default, the block will contain 10 feed items.
|
||||
return [
|
||||
'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 = [];
|
||||
foreach ($feeds as $feed) {
|
||||
$options[$feed->id()] = $feed->label();
|
||||
}
|
||||
$form['feed'] = [
|
||||
'#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'] = [
|
||||
'#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->itemStorage->getQuery()
|
||||
->condition('fid', $feed->id())
|
||||
->range(0, $this->configuration['block_count'])
|
||||
->sort('timestamp', 'DESC')
|
||||
->sort('iid', 'DESC')
|
||||
->execute();
|
||||
|
||||
if ($result) {
|
||||
// Only display the block if there are items to show.
|
||||
$items = $this->itemStorage->loadMultiple($result);
|
||||
|
||||
$build['list'] = [
|
||||
'#theme' => 'item_list',
|
||||
'#items' => [],
|
||||
];
|
||||
foreach ($items as $item) {
|
||||
$build['list']['#items'][$item->id()] = [
|
||||
'#type' => 'link',
|
||||
'#url' => $item->urlInfo(),
|
||||
'#title' => $item->label(),
|
||||
];
|
||||
}
|
||||
$build['more_link'] = [
|
||||
'#type' => 'more_link',
|
||||
'#url' => $feed->urlInfo(),
|
||||
'#attributes' => ['title' => $this->t("View this feed's recent news.")],
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheTags() {
|
||||
$cache_tags = parent::getCacheTags();
|
||||
if ($feed = $this->feedStorage->load($this->configuration['feed'])) {
|
||||
$cache_tags = Cache::mergeTags($cache_tags, $feed->getCacheTags());
|
||||
}
|
||||
return $cache_tags;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator fetcher implementations.
|
||||
*
|
||||
* A fetcher downloads feed data to a Drupal site. The fetcher is called at the
|
||||
* first of the three aggregation stages: first, data is downloaded by the
|
||||
* active fetcher; second, it is converted to a common format by the active
|
||||
* parser; and finally, it is passed to all active processors, which manipulate
|
||||
* or store the data.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorFetcher
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface FetcherInterface {
|
||||
|
||||
/**
|
||||
* Downloads feed data.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* A feed object representing the resource to be downloaded.
|
||||
* $feed->getUrl() contains the link to the feed.
|
||||
* Download the data at the URL and expose it
|
||||
* to other modules by attaching it to $feed->source_string.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if fetching was successful, FALSE otherwise.
|
||||
*/
|
||||
public function fetch(FeedInterface $feed);
|
||||
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'aggregator_title' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "aggregator_title",
|
||||
* label = @Translation("Aggregator title"),
|
||||
* description = @Translation("Formats an aggregator item or feed title with an optional link."),
|
||||
* field_types = {
|
||||
* "string"
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class AggregatorTitleFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function defaultSettings() {
|
||||
$options = parent::defaultSettings();
|
||||
|
||||
$options['display_as_link'] = TRUE;
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function settingsForm(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::settingsForm($form, $form_state);
|
||||
|
||||
$form['display_as_link'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Link to URL'),
|
||||
'#default_value' => $this->getSetting('display_as_link'),
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
if ($items->getEntity()->getEntityTypeId() == 'aggregator_feed') {
|
||||
$url_string = $items->getEntity()->getUrl();
|
||||
}
|
||||
else {
|
||||
$url_string = $items->getEntity()->getLink();
|
||||
}
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
if ($this->getSetting('display_as_link') && $url_string) {
|
||||
$elements[$delta] = [
|
||||
'#type' => 'link',
|
||||
'#title' => $item->value,
|
||||
'#url' => Url::fromUri($url_string),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$elements[$delta] = ['#markup' => $item->value];
|
||||
}
|
||||
}
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function isApplicable(FieldDefinitionInterface $field_definition) {
|
||||
return (($field_definition->getTargetEntityTypeId() === 'aggregator_item' || $field_definition->getTargetEntityTypeId() === 'aggregator_feed') && $field_definition->getName() === 'title');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin\Field\FieldFormatter;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FormatterBase;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'aggregator_xss' formatter.
|
||||
*
|
||||
* @FieldFormatter(
|
||||
* id = "aggregator_xss",
|
||||
* label = @Translation("Aggregator XSS"),
|
||||
* description = @Translation("Filter output for aggregator items"),
|
||||
* field_types = {
|
||||
* "string",
|
||||
* "string_long",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class AggregatorXSSFormatter extends FormatterBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewElements(FieldItemListInterface $items, $langcode) {
|
||||
$elements = [];
|
||||
|
||||
foreach ($items as $delta => $item) {
|
||||
$elements[$delta] = [
|
||||
'#type' => 'markup',
|
||||
'#markup' => $item->value,
|
||||
'#allowed_tags' => _aggregator_allowed_tags(),
|
||||
];
|
||||
}
|
||||
return $elements;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator parser implementations.
|
||||
*
|
||||
* A parser converts feed item data to a common format. The parser is called
|
||||
* at the second of the three aggregation stages: first, data is downloaded
|
||||
* by the active fetcher; second, it is converted to a common format by the
|
||||
* active parser; and finally, it is passed to all active processors which
|
||||
* manipulate or store the data.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorParser
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface ParserInterface {
|
||||
|
||||
/**
|
||||
* Parses feed data.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* An object describing the resource to be parsed.
|
||||
* $feed->source_string->value contains the raw feed data. Parse the data
|
||||
* and add the following properties to the $feed object:
|
||||
* - description: The human-readable description of the feed.
|
||||
* - link: A full URL that directly relates to the feed.
|
||||
* - image: An image URL used to display an image of the feed.
|
||||
* - etag: An entity tag from the HTTP header used for cache validation to
|
||||
* determine if the content has been changed.
|
||||
* - modified: The UNIX timestamp when the feed was last modified.
|
||||
* - items: An array of feed items. The common format for a single feed item
|
||||
* is an associative array containing:
|
||||
* - title: The human-readable title of the feed item.
|
||||
* - description: The full body text of the item or a summary.
|
||||
* - timestamp: The UNIX timestamp when the feed item was last published.
|
||||
* - author: The author of the feed item.
|
||||
* - guid: The global unique identifier (GUID) string that uniquely
|
||||
* identifies the item. If not available, the link is used to identify
|
||||
* the item.
|
||||
* - link: A full URL to the individual feed item.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if parsing was successful, FALSE otherwise.
|
||||
*/
|
||||
public function parse(FeedInterface $feed);
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Plugin;
|
||||
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines an interface for aggregator processor implementations.
|
||||
*
|
||||
* A processor acts on parsed feed data. Active processors are called at the
|
||||
* third and last of the aggregation stages: first, data is downloaded by the
|
||||
* active fetcher; second, it is converted to a common format by the active
|
||||
* parser; and finally, it is passed to all active processors that manipulate or
|
||||
* store the data.
|
||||
*
|
||||
* @see \Drupal\aggregator\Annotation\AggregatorProcessor
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginSettingsBase
|
||||
* @see \Drupal\aggregator\Plugin\AggregatorPluginManager
|
||||
* @see plugin_api
|
||||
*/
|
||||
interface ProcessorInterface {
|
||||
|
||||
/**
|
||||
* Processes feed data.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* A feed object representing the resource to be processed.
|
||||
* $feed->items contains an array of feed items downloaded and parsed at the
|
||||
* parsing stage. See \Drupal\aggregator\Plugin\FetcherInterface::parse()
|
||||
* for the basic format of a single item in the $feed->items array.
|
||||
* For the exact format refer to the particular parser in use.
|
||||
*/
|
||||
public function process(FeedInterface $feed);
|
||||
|
||||
/**
|
||||
* Refreshes feed information.
|
||||
*
|
||||
* Called after the processing of the feed is completed by all selected
|
||||
* processors.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Object describing feed.
|
||||
*
|
||||
* @see aggregator_refresh()
|
||||
*/
|
||||
public function postProcess(FeedInterface $feed);
|
||||
|
||||
/**
|
||||
* Deletes stored feed data.
|
||||
*
|
||||
* Called by aggregator if either a feed is deleted or a user clicks on
|
||||
* "delete items".
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* The $feed object whose items are being deleted.
|
||||
*/
|
||||
public function delete(FeedInterface $feed);
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue