Update core 8.3.0
This commit is contained in:
parent
da7a7918f8
commit
cd7a898e66
6144 changed files with 132297 additions and 87747 deletions
|
@ -15,13 +15,13 @@ function action_help($route_name, RouteMatchInterface $route_match) {
|
|||
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>.', array(':documentation' => 'https://www.drupal.org/documentation/modules/action')) . '</p>';
|
||||
$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>.', array(':actions' => \Drupal::url('entity.action.collection'))) . '</dd>';
|
||||
$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.', array(':actions' => \Drupal::url('entity.action.collection'))) . '</dd>';
|
||||
$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;
|
||||
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
* Implements hook_views_form_substitutions().
|
||||
*/
|
||||
function action_views_form_substitutions() {
|
||||
$select_all = array(
|
||||
$select_all = [
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => FALSE,
|
||||
'#attributes' => array('class' => array('action-table-select-all')),
|
||||
);
|
||||
return array(
|
||||
'#attributes' => ['class' => ['action-table-select-all']],
|
||||
];
|
||||
return [
|
||||
'<!--action-bulk-form-select-all-->' => drupal_render($select_all),
|
||||
);
|
||||
];
|
||||
}
|
||||
|
|
|
@ -58,32 +58,32 @@ abstract class ActionFormBase extends EntityForm {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form['label'] = array(
|
||||
$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'] = array(
|
||||
$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' => array(
|
||||
'exists' => array($this, 'exists'),
|
||||
),
|
||||
);
|
||||
$form['plugin'] = array(
|
||||
'#machine_name' => [
|
||||
'exists' => [$this, 'exists'],
|
||||
],
|
||||
];
|
||||
$form['plugin'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $this->entity->get('plugin'),
|
||||
);
|
||||
$form['type'] = array(
|
||||
];
|
||||
$form['type'] = [
|
||||
'#type' => 'value',
|
||||
'#value' => $this->entity->getType(),
|
||||
);
|
||||
];
|
||||
|
||||
if ($this->plugin instanceof PluginFormInterface) {
|
||||
$form += $this->plugin->buildConfigurationForm($form, $form_state);
|
||||
|
|
|
@ -86,10 +86,10 @@ class ActionListBuilder extends ConfigEntityListBuilder {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header = array(
|
||||
$header = [
|
||||
'type' => t('Action type'),
|
||||
'label' => t('Label'),
|
||||
) + parent::buildHeader();
|
||||
] + parent::buildHeader();
|
||||
return $header;
|
||||
}
|
||||
|
||||
|
@ -97,7 +97,7 @@ class ActionListBuilder extends ConfigEntityListBuilder {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$operations = $entity->isConfigurable() ? parent::getDefaultOperations($entity) : array();
|
||||
$operations = $entity->isConfigurable() ? parent::getDefaultOperations($entity) : [];
|
||||
if (isset($operations['edit'])) {
|
||||
$operations['edit']['title'] = t('Configure');
|
||||
}
|
||||
|
|
|
@ -50,33 +50,33 @@ class ActionAdminManageForm extends FormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$actions = array();
|
||||
$actions = [];
|
||||
foreach ($this->manager->getDefinitions() as $id => $definition) {
|
||||
if (is_subclass_of($definition['class'], '\Drupal\Core\Plugin\PluginFormInterface')) {
|
||||
$key = Crypt::hashBase64($id);
|
||||
$actions[$key] = $definition['label'] . '...';
|
||||
}
|
||||
}
|
||||
$form['parent'] = array(
|
||||
$form['parent'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Create an advanced action'),
|
||||
'#attributes' => array('class' => array('container-inline')),
|
||||
'#attributes' => ['class' => ['container-inline']],
|
||||
'#open' => TRUE,
|
||||
);
|
||||
$form['parent']['action'] = array(
|
||||
];
|
||||
$form['parent']['action'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => $this->t('Action'),
|
||||
'#title_display' => 'invisible',
|
||||
'#options' => $actions,
|
||||
'#empty_option' => $this->t('Choose an advanced action'),
|
||||
);
|
||||
$form['parent']['actions'] = array(
|
||||
];
|
||||
$form['parent']['actions'] = [
|
||||
'#type' => 'actions'
|
||||
);
|
||||
$form['parent']['actions']['submit'] = array(
|
||||
];
|
||||
$form['parent']['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Create'),
|
||||
);
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ class ActionAdminManageForm extends FormBase {
|
|||
if ($form_state->getValue('action')) {
|
||||
$form_state->setRedirect(
|
||||
'action.admin_add',
|
||||
array('action_id' => $form_state->getValue('action'))
|
||||
['action_id' => $form_state->getValue('action')]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ class EmailAction extends ConfigurableActionBase implements ContainerFactoryPlug
|
|||
// 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(array('mail' => $recipient));
|
||||
$recipient_accounts = $this->storage->loadByProperties(['mail' => $recipient]);
|
||||
$recipient_account = reset($recipient_accounts);
|
||||
if ($recipient_account) {
|
||||
$langcode = $recipient_account->getPreferredLangcode();
|
||||
|
@ -137,13 +137,13 @@ class EmailAction extends ConfigurableActionBase implements ContainerFactoryPlug
|
|||
else {
|
||||
$langcode = $this->languageManager->getDefaultLanguage()->getId();
|
||||
}
|
||||
$params = array('context' => $this->configuration);
|
||||
$params = ['context' => $this->configuration];
|
||||
|
||||
if ($this->mailManager->mail('system', 'action_send_email', $recipient, $langcode, $params)) {
|
||||
$this->logger->notice('Sent email to %recipient', array('%recipient' => $recipient));
|
||||
$this->logger->notice('Sent email to %recipient', ['%recipient' => $recipient]);
|
||||
}
|
||||
else {
|
||||
$this->logger->error('Unable to send email to %recipient', array('%recipient' => $recipient));
|
||||
$this->logger->error('Unable to send email to %recipient', ['%recipient' => $recipient]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,39 +151,39 @@ class EmailAction extends ConfigurableActionBase implements ContainerFactoryPlug
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array(
|
||||
return [
|
||||
'recipient' => '',
|
||||
'subject' => '',
|
||||
'message' => '',
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['recipient'] = array(
|
||||
$form['recipient'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Recipient'),
|
||||
'#title' => t('Recipient email address'),
|
||||
'#default_value' => $this->configuration['recipient'],
|
||||
'#maxlength' => '254',
|
||||
'#description' => t('The email address to which the message should be sent OR enter [node:author:mail], [comment:author:mail], etc. if you would like to send an email to the author of the original post.'),
|
||||
);
|
||||
$form['subject'] = array(
|
||||
'#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'] = array(
|
||||
];
|
||||
$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;
|
||||
}
|
||||
|
||||
|
@ -193,7 +193,7 @@ class EmailAction extends ConfigurableActionBase implements ContainerFactoryPlug
|
|||
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.', array('%author' => '[node:author:mail]')));
|
||||
$form_state->setErrorByName('recipient', t('Enter a valid email address or use a token email address such as %author.', ['%author' => '[node:author:mail]']));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -103,22 +103,22 @@ class GotoAction extends ConfigurableActionBase implements ContainerFactoryPlugi
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array(
|
||||
return [
|
||||
'url' => '',
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['url'] = array(
|
||||
$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.', array('@url' => 'http://example.com')),
|
||||
'#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,23 +84,23 @@ class MessageAction extends ConfigurableActionBase implements ContainerFactoryPl
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array(
|
||||
return [
|
||||
'message' => '',
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
|
||||
$form['message'] = array(
|
||||
$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;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,12 +27,12 @@ class Action extends DrupalSqlBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = array(
|
||||
$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');
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\action\Tests;
|
||||
namespace Drupal\Tests\action\Functional;
|
||||
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Test behaviors when visiting the action listing page.
|
||||
*
|
||||
* @group action
|
||||
*/
|
||||
class ActionListTest extends WebTestBase {
|
||||
class ActionListTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('action');
|
||||
public static $modules = ['action'];
|
||||
|
||||
/**
|
||||
* Tests the behavior when there are no actions to list in the admin page.
|
|
@ -17,19 +17,19 @@ class ActionUninstallTest extends BrowserTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('views', 'action');
|
||||
public static $modules = ['views', 'action'];
|
||||
|
||||
/**
|
||||
* Tests Action uninstall.
|
||||
*/
|
||||
public function testActionUninstall() {
|
||||
\Drupal::service('module_installer')->uninstall(array('action'));
|
||||
\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(array('administer users'));
|
||||
$admin_user = $this->drupalCreateUser(['administer users']);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->drupalGet('admin/people');
|
||||
|
|
|
@ -18,7 +18,7 @@ class BulkFormTest extends BrowserTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('node', 'action_bulk_test');
|
||||
public static $modules = ['node', 'action_bulk_test'];
|
||||
|
||||
/**
|
||||
* Tests the bulk form.
|
||||
|
@ -31,30 +31,30 @@ class BulkFormTest extends BrowserTestBase {
|
|||
$this->drupalGet('test_bulk_form_empty');
|
||||
$this->assertText(t('This view is empty.'), 'Empty text found on empty bulk form.');
|
||||
|
||||
$nodes = array();
|
||||
$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(array(
|
||||
$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]', array(':id' => 'edit-header'));
|
||||
$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 = array();
|
||||
$edit = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$this->assertFieldById('edit-node-bulk-form-' . $i, NULL, format_string('The checkbox on row @row appears.', array('@row' => $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;
|
||||
}
|
||||
|
||||
|
@ -67,12 +67,12 @@ class BulkFormTest extends BrowserTestBase {
|
|||
$this->drupalGet('test_bulk_form');
|
||||
|
||||
// Set all nodes to sticky and check that.
|
||||
$edit += array('action' => 'node_make_sticky_action');
|
||||
$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.', array('@nid' => $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.');
|
||||
|
@ -81,18 +81,18 @@ class BulkFormTest extends BrowserTestBase {
|
|||
$node = $node_storage->load($nodes[0]->id());
|
||||
$this->assertTrue($node->isPublished(), 'The node is published.');
|
||||
|
||||
$edit = array('node_bulk_form[0]' => TRUE, 'action' => 'node_unpublish_action');
|
||||
$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(array($node->id()));
|
||||
$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(array($nodes[1]->id()));
|
||||
$node_storage->resetCache([$nodes[1]->id()]);
|
||||
$node = $node_storage->load($nodes[1]->id());
|
||||
$this->assertTrue($node->isPublished(), 'An unchecked node is still published.');
|
||||
|
||||
|
@ -105,7 +105,7 @@ class BulkFormTest extends BrowserTestBase {
|
|||
$view->save();
|
||||
|
||||
$this->drupalGet('test_bulk_form');
|
||||
$options = $this->xpath('//select[@id=:id]/option', array(':id' => 'edit-action'));
|
||||
$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');
|
||||
|
@ -137,17 +137,17 @@ class BulkFormTest extends BrowserTestBase {
|
|||
|
||||
$this->drupalGet('test_bulk_form');
|
||||
// Call the node delete action.
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$edit["node_bulk_form[$i]"] = TRUE;
|
||||
}
|
||||
$edit += array('action' => 'node_delete_action');
|
||||
$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, array(), t('Delete'));
|
||||
$this->drupalPostForm(NULL, [], t('Delete'));
|
||||
$this->assertText(t('Deleted 5 posts.'));
|
||||
// Check if we got redirected to the original page.
|
||||
$this->assertUrl('test_bulk_form');
|
||||
|
|
|
@ -19,24 +19,24 @@ class ConfigurationTest extends BrowserTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('action');
|
||||
public static $modules = ['action'];
|
||||
|
||||
/**
|
||||
* Tests configuration of advanced actions through administration interface.
|
||||
*/
|
||||
function testActionConfiguration() {
|
||||
public function testActionConfiguration() {
|
||||
// Create a user with permission to view the actions administration pages.
|
||||
$user = $this->drupalCreateUser(array('administer actions'));
|
||||
$user = $this->drupalCreateUser(['administer actions']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Make a POST request to admin/config/system/actions.
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$edit['action'] = Crypt::hashBase64('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 = array();
|
||||
$edit = [];
|
||||
$action_label = $this->randomMachineName();
|
||||
$edit['label'] = $action_label;
|
||||
$edit['id'] = strtolower($action_label);
|
||||
|
@ -52,7 +52,7 @@ class ConfigurationTest extends BrowserTestBase {
|
|||
$this->clickLink(t('Configure'));
|
||||
preg_match('|admin/config/system/actions/configure/(.+)|', $this->getUrl(), $matches);
|
||||
$aid = $matches[1];
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$new_action_label = $this->randomMachineName();
|
||||
$edit['label'] = $new_action_label;
|
||||
$edit['url'] = 'admin';
|
||||
|
@ -72,12 +72,12 @@ class ConfigurationTest extends BrowserTestBase {
|
|||
$this->drupalGet('admin/config/system/actions');
|
||||
$this->clickLink(t('Delete'));
|
||||
$this->assertResponse(200);
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$this->drupalPostForm("admin/config/system/actions/configure/$aid/delete", $edit, t('Delete'));
|
||||
$this->assertResponse(200);
|
||||
|
||||
// Make sure that the action was actually deleted.
|
||||
$this->assertRaw(t('The action %action has been deleted.', array('%action' => $new_action_label)), 'Make sure that we get a delete confirmation message.');
|
||||
$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.");
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Drupal\Tests\action\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\config\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Drupal\Tests\action\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\config\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,7 +12,7 @@ use Drupal\Tests\Core\Menu\LocalTaskIntegrationTestBase;
|
|||
class ActionLocalTasksTest extends LocalTaskIntegrationTestBase {
|
||||
|
||||
protected function setUp() {
|
||||
$this->directoryList = array('action' => 'core/modules/action');
|
||||
$this->directoryList = ['action' => 'core/modules/action'];
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ class ActionLocalTasksTest extends LocalTaskIntegrationTestBase {
|
|||
* Tests local task existence.
|
||||
*/
|
||||
public function testActionLocalTasks() {
|
||||
$this->assertLocalTasks('entity.action.collection', array(array('action.admin')));
|
||||
$this->assertLocalTasks('entity.action.collection', [['action.admin']]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,23 +10,18 @@
|
|||
*/
|
||||
function aggregator_requirements($phase) {
|
||||
$has_curl = function_exists('curl_init');
|
||||
$requirements = array();
|
||||
$requirements['curl'] = array(
|
||||
$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 could not be installed because the PHP <a href="http://php.net/manual/curl.setup.php">cURL</a> library is not available.');
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.0.0-rc
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* The simple presence of this update function clears cached field definitions.
|
||||
*/
|
||||
|
@ -34,15 +29,6 @@ function aggregator_update_8001() {
|
|||
// Feed ID base field is now required.
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.0.0-rc".
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.2.x
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Make the 'Source feed' field for aggregator items required.
|
||||
*/
|
||||
|
@ -54,7 +40,3 @@ function aggregator_update_8200() {
|
|||
$field_definition->setRequired(TRUE);
|
||||
$definition_update_manager->updateFieldStorageDefinition($field_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.2.x".
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,9 @@ 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.
|
||||
*/
|
||||
const AGGREGATOR_CLEAR_NEVER = 0;
|
||||
|
||||
|
@ -22,35 +25,35 @@ function aggregator_help($route_name, RouteMatchInterface $route_match) {
|
|||
$path_validator = \Drupal::pathValidator();
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Aggregator module is an on-site syndicator and news reader that gathers and displays fresh content from RSS-, RDF-, and Atom-based feeds made available across the web. Thousands of sites (particularly news sites and blogs) publish their latest headlines in feeds, using a number of standardized XML-based formats. For more information, see the <a href=":aggregator-module">online documentation for the Aggregator module</a>.', array(':aggregator-module' => 'https://www.drupal.org/documentation/modules/aggregator')) . '</p>';
|
||||
$output .= '<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>.', array(':aggregator' => \Drupal::url('aggregator.page_last'), ':aggregator-sources' => $url->toString(), ':admin-block' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#')) . '</dd>';
|
||||
$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>.', array(':feededit' => \Drupal::url('aggregator.admin_overview'))) . '</dd>';
|
||||
$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>.', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '</dd>';
|
||||
$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).', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '<dd>';
|
||||
$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>.', array(':aggregator-opml' => $url->toString(), ':import-opml' => \Drupal::url('aggregator.opml_add'))) . '</dd>';
|
||||
$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.', array(':cron' => \Drupal::url('system.cron_settings'))) . '</dd>';
|
||||
$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>.', array(':addfeed' => \Drupal::url('aggregator.feed_add'), ':block' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#')) . '</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':
|
||||
|
@ -65,66 +68,66 @@ function aggregator_help($route_name, RouteMatchInterface $route_match) {
|
|||
* Implements hook_theme().
|
||||
*/
|
||||
function aggregator_theme() {
|
||||
return array(
|
||||
'aggregator_feed' => array(
|
||||
return [
|
||||
'aggregator_feed' => [
|
||||
'render element' => 'elements',
|
||||
'file' => 'aggregator.theme.inc',
|
||||
),
|
||||
'aggregator_item' => array(
|
||||
],
|
||||
'aggregator_item' => [
|
||||
'render element' => 'elements',
|
||||
'file' => 'aggregator.theme.inc',
|
||||
),
|
||||
);
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_extra_field_info().
|
||||
*/
|
||||
function aggregator_entity_extra_field_info() {
|
||||
$extra = array();
|
||||
$extra = [];
|
||||
|
||||
$extra['aggregator_feed']['aggregator_feed'] = array(
|
||||
'display' => array(
|
||||
'items' => array(
|
||||
$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' => array(
|
||||
'image' => [
|
||||
'label' => t('Image'),
|
||||
'description' => t('The feed image'),
|
||||
'weight' => 2,
|
||||
),
|
||||
],
|
||||
// @todo Move to a formatter at https://www.drupal.org/node/2149845.
|
||||
'description' => array(
|
||||
'description' => [
|
||||
'label' => t('Description'),
|
||||
'description' => t('The description of this feed'),
|
||||
'weight' => 3,
|
||||
),
|
||||
'more_link' => array(
|
||||
],
|
||||
'more_link' => [
|
||||
'label' => t('More link'),
|
||||
'description' => t('A more link to the feed detail page'),
|
||||
'weight' => 5,
|
||||
),
|
||||
'feed_icon' => array(
|
||||
],
|
||||
'feed_icon' => [
|
||||
'label' => t('Feed icon'),
|
||||
'description' => t('An icon that links to the feed URL'),
|
||||
'weight' => 6,
|
||||
),
|
||||
),
|
||||
);
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$extra['aggregator_item']['aggregator_item'] = array(
|
||||
'display' => array(
|
||||
$extra['aggregator_item']['aggregator_item'] = [
|
||||
'display' => [
|
||||
// @todo Move to a formatter at https://www.drupal.org/node/2149845.
|
||||
'description' => array(
|
||||
'description' => [
|
||||
'label' => t('Description'),
|
||||
'description' => t('The description of this feed item'),
|
||||
'weight' => 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return $extra;
|
||||
}
|
||||
|
|
|
@ -11,20 +11,26 @@ 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
|
||||
|
|
|
@ -12,8 +12,10 @@ mode: summary
|
|||
content:
|
||||
items:
|
||||
weight: 0
|
||||
region: content
|
||||
more_link:
|
||||
weight: 1
|
||||
region: content
|
||||
hidden:
|
||||
checked: true
|
||||
description: true
|
||||
|
|
|
@ -12,6 +12,7 @@ mode: summary
|
|||
content:
|
||||
timestamp:
|
||||
weight: 0
|
||||
region: content
|
||||
hidden:
|
||||
author: true
|
||||
description: true
|
||||
|
|
|
@ -15,12 +15,12 @@ class AggregatorFeedViewsData extends EntityViewsData {
|
|||
public function getViewsData() {
|
||||
$data = parent::getViewsData();
|
||||
|
||||
$data['aggregator_feed']['table']['join'] = array(
|
||||
'aggregator_item' => array(
|
||||
$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';
|
||||
|
|
|
@ -48,9 +48,9 @@ class AggregatorController extends ControllerBase {
|
|||
*/
|
||||
public function feedAdd() {
|
||||
$feed = $this->entityManager()->getStorage('aggregator_feed')
|
||||
->create(array(
|
||||
->create([
|
||||
'refresh' => 3600,
|
||||
));
|
||||
]);
|
||||
return $this->entityFormBuilder()->getForm($feed);
|
||||
}
|
||||
|
||||
|
@ -67,15 +67,15 @@ class AggregatorController extends ControllerBase {
|
|||
*/
|
||||
protected function buildPageList(array $items, $feed_source = '') {
|
||||
// Assemble output.
|
||||
$build = array(
|
||||
$build = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => array('class' => array('aggregator-wrapper')),
|
||||
);
|
||||
$build['feed_source'] = is_array($feed_source) ? $feed_source : array('#markup' => $feed_source);
|
||||
'#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'] = array('#type' => 'pager');
|
||||
$build['pager'] = ['#type' => 'pager'];
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
@ -94,8 +94,8 @@ class AggregatorController extends ControllerBase {
|
|||
*/
|
||||
public function feedRefresh(FeedInterface $aggregator_feed) {
|
||||
$message = $aggregator_feed->refreshItems()
|
||||
? $this->t('There is new syndicated content from %site.', array('%site' => $aggregator_feed->label()))
|
||||
: $this->t('There is no new syndicated content from %site.', array('%site' => $aggregator_feed->label()));
|
||||
? $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()]);
|
||||
drupal_set_message($message);
|
||||
return $this->redirect('aggregator.admin_overview');
|
||||
}
|
||||
|
@ -111,22 +111,22 @@ class AggregatorController extends ControllerBase {
|
|||
$feeds = $entity_manager->getStorage('aggregator_feed')
|
||||
->loadMultiple();
|
||||
|
||||
$header = array($this->t('Title'), $this->t('Items'), $this->t('Last update'), $this->t('Next update'), $this->t('Operations'));
|
||||
$rows = array();
|
||||
$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 = array();
|
||||
$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', array('@time' => $this->dateFormatter->formatInterval(REQUEST_TIME - $last_checked))) : $this->t('never'));
|
||||
$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', array('%time' => $this->dateFormatter->formatInterval($last_checked + $refresh_rate - REQUEST_TIME)));
|
||||
$next_update = $next = $this->t('%time left', ['%time' => $this->dateFormatter->formatInterval($last_checked + $refresh_rate - REQUEST_TIME)]);
|
||||
}
|
||||
else {
|
||||
$next_update = $this->t('never');
|
||||
|
@ -136,33 +136,33 @@ class AggregatorController extends ControllerBase {
|
|||
'title' => $this->t('Edit'),
|
||||
'url' => Url::fromRoute('entity.aggregator_feed.edit_form', ['aggregator_feed' => $feed->id()]),
|
||||
];
|
||||
$links['delete'] = array(
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => Url::fromRoute('entity.aggregator_feed.delete_form', ['aggregator_feed' => $feed->id()]),
|
||||
);
|
||||
$links['delete_items'] = array(
|
||||
];
|
||||
$links['delete_items'] = [
|
||||
'title' => $this->t('Delete items'),
|
||||
'url' => Url::fromRoute('aggregator.feed_items_delete', ['aggregator_feed' => $feed->id()]),
|
||||
);
|
||||
$links['update'] = array(
|
||||
];
|
||||
$links['update'] = [
|
||||
'title' => $this->t('Update items'),
|
||||
'url' => Url::fromRoute('aggregator.feed_refresh', ['aggregator_feed' => $feed->id()]),
|
||||
);
|
||||
$row[] = array(
|
||||
'data' => array(
|
||||
];
|
||||
$row[] = [
|
||||
'data' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
),
|
||||
);
|
||||
],
|
||||
];
|
||||
$rows[] = $row;
|
||||
}
|
||||
$build['feeds'] = array(
|
||||
$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>.', array(':link' => $this->url('aggregator.feed_add'))),
|
||||
);
|
||||
'#empty' => $this->t('No feeds available. <a href=":link">Add feed</a>.', [':link' => $this->url('aggregator.feed_add')]),
|
||||
];
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ class AggregatorController extends ControllerBase {
|
|||
public function pageLast() {
|
||||
$items = $this->entityManager()->getStorage('aggregator_item')->loadAll(20);
|
||||
$build = $this->buildPageList($items);
|
||||
$build['#attached']['feed'][] = array('aggregator/rss', $this->config('system.site')->get('name') . ' ' . $this->t('aggregator'));
|
||||
$build['#attached']['feed'][] = ['aggregator/rss', $this->config('system.site')->get('name') . ' ' . $this->t('aggregator')];
|
||||
return $build;
|
||||
}
|
||||
|
||||
|
|
|
@ -88,11 +88,11 @@ class Feed extends ContentEntityBase implements FeedInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public static function preCreate(EntityStorageInterface $storage, array &$values) {
|
||||
$values += array(
|
||||
$values += [
|
||||
'link' => '',
|
||||
'description' => '',
|
||||
'image' => '',
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -143,10 +143,10 @@ class Feed extends ContentEntityBase implements FeedInterface {
|
|||
->setDescription(t('The name of the feed (or the name of the website providing the feed).'))
|
||||
->setRequired(TRUE)
|
||||
->setSetting('max_length', 255)
|
||||
->setDisplayOptions('form', array(
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'string_textfield',
|
||||
'weight' => -5,
|
||||
))
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->addConstraint('FeedTitle');
|
||||
|
||||
|
@ -154,15 +154,15 @@ class Feed extends ContentEntityBase implements FeedInterface {
|
|||
->setLabel(t('URL'))
|
||||
->setDescription(t('The fully-qualified URL of the feed.'))
|
||||
->setRequired(TRUE)
|
||||
->setDisplayOptions('form', array(
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'uri',
|
||||
'weight' => -3,
|
||||
))
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE)
|
||||
->addConstraint('FeedUrl');
|
||||
|
||||
$intervals = array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200);
|
||||
$period = array_map(array(\Drupal::service('date.formatter'), 'formatInterval'), array_combine($intervals, $intervals));
|
||||
$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')
|
||||
|
@ -171,21 +171,21 @@ class Feed extends ContentEntityBase implements FeedInterface {
|
|||
->setSetting('unsigned', TRUE)
|
||||
->setRequired(TRUE)
|
||||
->setSetting('allowed_values', $period)
|
||||
->setDisplayOptions('form', array(
|
||||
->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', array(
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'inline',
|
||||
'type' => 'timestamp_ago',
|
||||
'weight' => 1,
|
||||
))
|
||||
])
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
$fields['queued'] = BaseFieldDefinition::create('timestamp')
|
||||
|
@ -196,15 +196,15 @@ class Feed extends ContentEntityBase implements FeedInterface {
|
|||
$fields['link'] = BaseFieldDefinition::create('uri')
|
||||
->setLabel(t('URL'))
|
||||
->setDescription(t('The link of the feed.'))
|
||||
->setDisplayOptions('view', array(
|
||||
->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.", array('@description' => '<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'))
|
||||
|
|
|
@ -61,11 +61,11 @@ class Item extends ContentEntityBase implements ItemInterface {
|
|||
->setRequired(TRUE)
|
||||
->setDescription(t('The aggregator feed entity associated with this item.'))
|
||||
->setSetting('target_type', 'aggregator_feed')
|
||||
->setDisplayOptions('view', array(
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'entity_reference_label',
|
||||
'weight' => 0,
|
||||
))
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['title'] = BaseFieldDefinition::create('string')
|
||||
|
@ -75,18 +75,18 @@ class Item extends ContentEntityBase implements ItemInterface {
|
|||
$fields['link'] = BaseFieldDefinition::create('uri')
|
||||
->setLabel(t('Link'))
|
||||
->setDescription(t('The link of the feed item.'))
|
||||
->setDisplayOptions('view', array(
|
||||
'type' => 'hidden',
|
||||
))
|
||||
->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', array(
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'weight' => 3,
|
||||
))
|
||||
])
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
$fields['description'] = BaseFieldDefinition::create('string_long')
|
||||
|
@ -96,11 +96,11 @@ class Item extends ContentEntityBase implements ItemInterface {
|
|||
$fields['timestamp'] = BaseFieldDefinition::create('created')
|
||||
->setLabel(t('Posted on'))
|
||||
->setDescription(t('Posted date of the feed item, as a Unix timestamp.'))
|
||||
->setDisplayOptions('view', array(
|
||||
->setDisplayOptions('view', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'timestamp_ago',
|
||||
'weight' => 1,
|
||||
))
|
||||
])
|
||||
->setDisplayConfigurable('view', TRUE);
|
||||
|
||||
// @todo Convert to a real UUID field in
|
||||
|
|
|
@ -20,12 +20,12 @@ class FeedForm extends ContentEntityForm {
|
|||
$label = $feed->label();
|
||||
$view_link = $feed->link($label, 'canonical');
|
||||
if ($status == SAVED_UPDATED) {
|
||||
drupal_set_message($this->t('The feed %feed has been updated.', array('%feed' => $view_link)));
|
||||
drupal_set_message($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.', array('%feed' => $feed->label(), 'link' => $this->l($this->t('View'), new Url('aggregator.admin_overview'))));
|
||||
drupal_set_message($this->t('The feed %feed has been added.', array('%feed' => $view_link)));
|
||||
$this->logger('aggregator')->notice('Feed %feed added.', ['%feed' => $feed->label(), 'link' => $this->l($this->t('View'), new Url('aggregator.admin_overview'))]);
|
||||
drupal_set_message($this->t('The feed %feed has been added.', ['%feed' => $view_link]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@ class FeedStorage extends SqlContentEntityStorage implements FeedStorageInterfac
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFeedIdsToRefresh() {
|
||||
return $this->database->query('SELECT fid FROM {aggregator_feed} WHERE queued = 0 AND checked + refresh < :time AND refresh <> :never', array(
|
||||
return $this->database->query('SELECT fid FROM {aggregator_feed} WHERE queued = 0 AND checked + refresh < :time AND refresh <> :never', [
|
||||
':time' => REQUEST_TIME,
|
||||
':never' => AGGREGATOR_CLEAR_NEVER,
|
||||
))->fetchCol();
|
||||
])->fetchCol();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,11 @@ use Drupal\Core\Entity\ContentEntityStorageInterface;
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -24,9 +24,6 @@ class FeedStorageSchema extends SqlContentEntityStorageSchema {
|
|||
break;
|
||||
|
||||
case 'queued':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
|
||||
break;
|
||||
|
||||
case 'title':
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema, TRUE);
|
||||
break;
|
||||
|
|
|
@ -68,65 +68,65 @@ class FeedViewBuilder extends EntityViewBuilder {
|
|||
|
||||
if ($view_mode == 'full') {
|
||||
// Also add the pager.
|
||||
$build[$id]['pager'] = array('#type' => 'pager');
|
||||
$build[$id]['pager'] = ['#type' => 'pager'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($display->getComponent('description')) {
|
||||
$build[$id]['description'] = array(
|
||||
$build[$id]['description'] = [
|
||||
'#markup' => $entity->getDescription(),
|
||||
'#allowed_tags' => _aggregator_allowed_tags(),
|
||||
'#prefix' => '<div class="feed-description">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
if ($display->getComponent('image')) {
|
||||
$image_link = array();
|
||||
$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 = array(
|
||||
$link_title = [
|
||||
'#theme' => 'image',
|
||||
'#uri' => $image,
|
||||
'#alt' => $label,
|
||||
);
|
||||
$image_link = array(
|
||||
];
|
||||
$image_link = [
|
||||
'#type' => 'link',
|
||||
'#title' => $link_title,
|
||||
'#url' => Url::fromUri($link_href),
|
||||
'#options' => array(
|
||||
'attributes' => array('class' => array('feed-image')),
|
||||
),
|
||||
);
|
||||
'#options' => [
|
||||
'attributes' => ['class' => ['feed-image']],
|
||||
],
|
||||
];
|
||||
}
|
||||
$build[$id]['image'] = $image_link;
|
||||
}
|
||||
|
||||
if ($display->getComponent('feed_icon')) {
|
||||
$build[$id]['feed_icon'] = array(
|
||||
$build[$id]['feed_icon'] = [
|
||||
'#theme' => 'feed_icon',
|
||||
'#url' => $entity->getUrl(),
|
||||
'#title' => t('@title feed', array('@title' => $entity->label())),
|
||||
);
|
||||
'#title' => t('@title feed', ['@title' => $entity->label()]),
|
||||
];
|
||||
}
|
||||
|
||||
if ($display->getComponent('more_link')) {
|
||||
$title_stripped = strip_tags($entity->label());
|
||||
$build[$id]['more_link'] = array(
|
||||
$build[$id]['more_link'] = [
|
||||
'#type' => 'link',
|
||||
'#title' => t('More<span class="visually-hidden"> posts about @title</span>', array(
|
||||
'#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' => array(
|
||||
'attributes' => array(
|
||||
'#options' => [
|
||||
'attributes' => [
|
||||
'title' => $title_stripped,
|
||||
),
|
||||
),
|
||||
);
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ class FeedDeleteForm extends ContentEntityDeleteForm {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDeletionMessage() {
|
||||
return $this->t('The feed %label has been deleted.', array(
|
||||
return $this->t('The feed %label has been deleted.', [
|
||||
'%label' => $this->entity->label(),
|
||||
));
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ class FeedItemsDeleteForm extends ContentEntityConfirmFormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to delete all items from the feed %feed?', array('%feed' => $this->entity->label()));
|
||||
return $this->t('Are you sure you want to delete all items from the feed %feed?', ['%feed' => $this->entity->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,33 +63,33 @@ class OpmlFeedAdd extends FormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$intervals = array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200);
|
||||
$period = array_map(array(\Drupal::service('date.formatter'), 'formatInterval'), array_combine($intervals, $intervals));
|
||||
$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'] = array(
|
||||
$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'] = array(
|
||||
];
|
||||
$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'] = array(
|
||||
];
|
||||
$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>.', array(':cron' => $this->url('system.status'))),
|
||||
);
|
||||
'#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'] = array('#type' => 'actions');
|
||||
$form['actions']['submit'] = array(
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Import'),
|
||||
);
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ class OpmlFeedAdd extends FormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$validators = array('file_validate_extensions' => array('opml xml'));
|
||||
$validators = ['file_validate_extensions' => ['opml xml']];
|
||||
if ($file = file_save_upload('upload', $validators, FALSE, 0)) {
|
||||
$data = file_get_contents($file->getFileUri());
|
||||
}
|
||||
|
@ -120,8 +120,8 @@ class OpmlFeedAdd extends FormBase {
|
|||
$data = (string) $response->getBody();
|
||||
}
|
||||
catch (RequestException $e) {
|
||||
$this->logger('aggregator')->warning('Failed to download OPML file due to "%error".', array('%error' => $e->getMessage()));
|
||||
drupal_set_message($this->t('Failed to download OPML file due to "%error".', array('%error' => $e->getMessage())));
|
||||
$this->logger('aggregator')->warning('Failed to download OPML file due to "%error".', ['%error' => $e->getMessage()]);
|
||||
drupal_set_message($this->t('Failed to download OPML file due to "%error".', ['%error' => $e->getMessage()]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ class OpmlFeedAdd extends FormBase {
|
|||
foreach ($feeds as $feed) {
|
||||
// Ensure URL is valid.
|
||||
if (!UrlHelper::isValid($feed['url'], TRUE)) {
|
||||
drupal_set_message($this->t('The URL %url is invalid.', array('%url' => $feed['url'])), 'warning');
|
||||
drupal_set_message($this->t('The URL %url is invalid.', ['%url' => $feed['url']]), 'warning');
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -151,20 +151,20 @@ class OpmlFeedAdd extends FormBase {
|
|||
$result = $this->feedStorage->loadMultiple($ids);
|
||||
foreach ($result as $old) {
|
||||
if (strcasecmp($old->label(), $feed['title']) == 0) {
|
||||
drupal_set_message($this->t('A feed named %title already exists.', array('%title' => $old->label())), 'warning');
|
||||
drupal_set_message($this->t('A feed named %title already exists.', ['%title' => $old->label()]), 'warning');
|
||||
continue 2;
|
||||
}
|
||||
if (strcasecmp($old->getUrl(), $feed['url']) == 0) {
|
||||
drupal_set_message($this->t('A feed with the URL %url already exists.', array('%url' => $old->getUrl())), 'warning');
|
||||
drupal_set_message($this->t('A feed with the URL %url already exists.', ['%url' => $old->getUrl()]), 'warning');
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
$new_feed = $this->feedStorage->create(array(
|
||||
$new_feed = $this->feedStorage->create([
|
||||
'title' => $feed['title'],
|
||||
'url' => $feed['url'],
|
||||
'refresh' => $form_state->getValue('refresh'),
|
||||
));
|
||||
]);
|
||||
$new_feed->save();
|
||||
}
|
||||
|
||||
|
@ -189,14 +189,15 @@ class OpmlFeedAdd extends FormBase {
|
|||
* @todo Move this to a parser in https://www.drupal.org/node/1963540.
|
||||
*/
|
||||
protected function parseOpml($opml) {
|
||||
$feeds = array();
|
||||
$xml_parser = drupal_xml_parser_create($opml);
|
||||
$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[] = array('title' => $item['TEXT'], 'url' => $item['XMLURL']);
|
||||
$feeds[] = ['title' => $item['TEXT'], 'url' => $item['XMLURL']];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,25 +21,25 @@ class SettingsForm extends ConfigFormBase {
|
|||
*
|
||||
* @var \Drupal\aggregator\Plugin\AggregatorPluginManager[]
|
||||
*/
|
||||
protected $managers = array();
|
||||
protected $managers = [];
|
||||
|
||||
/**
|
||||
* The instantiated plugin instances that have configuration forms.
|
||||
*
|
||||
* @var \Drupal\Core\Plugin\PluginFormInterface[]
|
||||
*/
|
||||
protected $configurableInstances = array();
|
||||
protected $configurableInstances = [];
|
||||
|
||||
/**
|
||||
* The aggregator plugin definitions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $definitions = array(
|
||||
'fetcher' => array(),
|
||||
'parser' => array(),
|
||||
'processor' => array(),
|
||||
);
|
||||
protected $definitions = [
|
||||
'fetcher' => [],
|
||||
'parser' => [],
|
||||
'processor' => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructs a \Drupal\aggregator\SettingsForm object.
|
||||
|
@ -58,15 +58,15 @@ class SettingsForm extends ConfigFormBase {
|
|||
public function __construct(ConfigFactoryInterface $config_factory, AggregatorPluginManager $fetcher_manager, AggregatorPluginManager $parser_manager, AggregatorPluginManager $processor_manager, TranslationInterface $string_translation) {
|
||||
parent::__construct($config_factory);
|
||||
$this->stringTranslation = $string_translation;
|
||||
$this->managers = array(
|
||||
$this->managers = [
|
||||
'fetcher' => $fetcher_manager,
|
||||
'parser' => $parser_manager,
|
||||
'processor' => $processor_manager,
|
||||
);
|
||||
];
|
||||
// Get all available fetcher, parser and processor definitions.
|
||||
foreach (array('fetcher', 'parser', 'processor') as $type) {
|
||||
foreach (['fetcher', 'parser', 'processor'] as $type) {
|
||||
foreach ($this->managers[$type]->getDefinitions() as $id => $definition) {
|
||||
$this->definitions[$type][$id] = SafeMarkup::format('@title <span class="description">@description</span>', array('@title' => $definition['title'], '@description' => $definition['description']));
|
||||
$this->definitions[$type][$id] = SafeMarkup::format('@title <span class="description">@description</span>', ['@title' => $definition['title'], '@description' => $definition['description']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -105,56 +105,56 @@ class SettingsForm extends ConfigFormBase {
|
|||
$config = $this->config('aggregator.settings');
|
||||
|
||||
// Global aggregator settings.
|
||||
$form['aggregator_allowed_html_tags'] = array(
|
||||
$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 = array();
|
||||
$basic_conf = [];
|
||||
if (count($this->definitions['fetcher']) > 1) {
|
||||
$basic_conf['aggregator_fetcher'] = array(
|
||||
$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'] = array(
|
||||
$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'] = array(
|
||||
$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'] = array(
|
||||
$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 (array('fetcher', 'parser') as $type) {
|
||||
foreach (['fetcher', 'parser'] as $type) {
|
||||
$active = $config->get($type);
|
||||
if (array_key_exists($active, $this->definitions[$type])) {
|
||||
$instance = $this->managers[$type]->createInstance($active);
|
||||
|
@ -169,7 +169,7 @@ class SettingsForm extends ConfigFormBase {
|
|||
}
|
||||
|
||||
// Implementing processor plugins will expect an array at $form['processors'].
|
||||
$form['processors'] = array();
|
||||
$form['processors'] = [];
|
||||
// Call buildConfigurationForm() for each active processor.
|
||||
foreach ($this->definitions['processor'] as $id => $definition) {
|
||||
if (in_array($id, $config->get('processors'))) {
|
||||
|
|
|
@ -20,12 +20,12 @@ class ItemViewBuilder extends EntityViewBuilder {
|
|||
$display = $displays[$bundle];
|
||||
|
||||
if ($display->getComponent('description')) {
|
||||
$build[$id]['description'] = array(
|
||||
$build[$id]['description'] = [
|
||||
'#markup' => $entity->getDescription(),
|
||||
'#allowed_tags' => _aggregator_allowed_tags(),
|
||||
'#prefix' => '<div class="item-description">',
|
||||
'#suffix' => '</div>',
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ class ItemsImporter implements ItemsImporterInterface {
|
|||
}
|
||||
|
||||
// Store instances in an array so we dont have to instantiate new objects.
|
||||
$processor_instances = array();
|
||||
$processor_instances = [];
|
||||
foreach ($this->config->get('processors') as $processor) {
|
||||
try {
|
||||
$processor_instances[$processor] = $this->processorManager->createInstance($processor);
|
||||
|
@ -124,10 +124,10 @@ class ItemsImporter implements ItemsImporterInterface {
|
|||
|
||||
// Log if feed URL has changed.
|
||||
if ($feed->getUrl() != $feed_url) {
|
||||
$this->logger->notice('Updated URL for feed %title to %url.', array('%title' => $feed->label(), '%url' => $feed->getUrl()));
|
||||
$this->logger->notice('Updated URL for feed %title to %url.', ['%title' => $feed->label(), '%url' => $feed->getUrl()]);
|
||||
}
|
||||
|
||||
$this->logger->notice('There is new syndicated content from %site.', array('%site' => $feed->label()));
|
||||
$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)) {
|
||||
|
|
|
@ -34,16 +34,16 @@ class AggregatorPluginManager extends DefaultPluginManager {
|
|||
* The module handler.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
$type_annotations = array(
|
||||
$type_annotations = [
|
||||
'fetcher' => 'Drupal\aggregator\Annotation\AggregatorFetcher',
|
||||
'parser' => 'Drupal\aggregator\Annotation\AggregatorParser',
|
||||
'processor' => 'Drupal\aggregator\Annotation\AggregatorProcessor',
|
||||
);
|
||||
$plugin_interfaces = array(
|
||||
];
|
||||
$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');
|
||||
|
|
|
@ -25,7 +25,7 @@ abstract class AggregatorPluginSettingsBase extends PluginBase implements Plugin
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return array();
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,7 +38,7 @@ abstract class AggregatorPluginSettingsBase extends PluginBase implements Plugin
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function calculateDependencies() {
|
||||
return array();
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ use Drupal\aggregator\ItemStorageInterface;
|
|||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
@ -38,13 +37,6 @@ class AggregatorFeedBlock extends BlockBase implements ContainerFactoryPluginInt
|
|||
*/
|
||||
protected $itemStorage;
|
||||
|
||||
/**
|
||||
* The entity query object for feed items.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryInterface
|
||||
*/
|
||||
protected $itemQuery;
|
||||
|
||||
/**
|
||||
* Constructs an AggregatorFeedBlock object.
|
||||
*
|
||||
|
@ -58,14 +50,11 @@ class AggregatorFeedBlock extends BlockBase implements ContainerFactoryPluginInt
|
|||
* The entity storage for feeds.
|
||||
* @param \Drupal\aggregator\ItemStorageInterface $item_storage
|
||||
* The entity storage for feed items.
|
||||
* @param \Drupal\Core\Entity\Query\QueryInterface $item_query
|
||||
* The entity query object for feed items.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, FeedStorageInterface $feed_storage, ItemStorageInterface $item_storage, QueryInterface $item_query) {
|
||||
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;
|
||||
$this->itemQuery = $item_query;
|
||||
}
|
||||
|
||||
|
||||
|
@ -77,9 +66,8 @@ class AggregatorFeedBlock extends BlockBase implements ContainerFactoryPluginInt
|
|||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('entity.manager')->getStorage('aggregator_feed'),
|
||||
$container->get('entity.manager')->getStorage('aggregator_item'),
|
||||
$container->get('entity.query')->get('aggregator_item')
|
||||
$container->get('entity_type.manager')->getStorage('aggregator_feed'),
|
||||
$container->get('entity_type.manager')->getStorage('aggregator_item')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -89,10 +77,10 @@ class AggregatorFeedBlock extends BlockBase implements ContainerFactoryPluginInt
|
|||
*/
|
||||
public function defaultConfiguration() {
|
||||
// By default, the block will contain 10 feed items.
|
||||
return array(
|
||||
return [
|
||||
'block_count' => 10,
|
||||
'feed' => NULL,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,23 +96,23 @@ class AggregatorFeedBlock extends BlockBase implements ContainerFactoryPluginInt
|
|||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$feeds = $this->feedStorage->loadMultiple();
|
||||
$options = array();
|
||||
$options = [];
|
||||
foreach ($feeds as $feed) {
|
||||
$options[$feed->id()] = $feed->label();
|
||||
}
|
||||
$form['feed'] = array(
|
||||
$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'] = array(
|
||||
$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;
|
||||
}
|
||||
|
||||
|
@ -142,7 +130,7 @@ class AggregatorFeedBlock extends BlockBase implements ContainerFactoryPluginInt
|
|||
public function build() {
|
||||
// Load the selected feed.
|
||||
if ($feed = $this->feedStorage->load($this->configuration['feed'])) {
|
||||
$result = $this->itemQuery
|
||||
$result = $this->itemStorage->getQuery()
|
||||
->condition('fid', $feed->id())
|
||||
->range(0, $this->configuration['block_count'])
|
||||
->sort('timestamp', 'DESC')
|
||||
|
|
|
@ -112,8 +112,8 @@ class DefaultFetcher implements FetcherInterface, ContainerFactoryPluginInterfac
|
|||
return TRUE;
|
||||
}
|
||||
catch (RequestException $e) {
|
||||
$this->logger->warning('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->label(), '%error' => $e->getMessage()));
|
||||
drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->label(), '%error' => $e->getMessage())), 'warning');
|
||||
$this->logger->warning('The feed from %site seems to be broken because of error "%error".', ['%site' => $feed->label(), '%error' => $e->getMessage()]);
|
||||
drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', ['%site' => $feed->label(), '%error' => $e->getMessage()]), 'warning');
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class DefaultParser implements ParserInterface {
|
|||
}
|
||||
catch (ExceptionInterface $e) {
|
||||
watchdog_exception('aggregator', $e);
|
||||
drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', array('%site' => $feed->label(), '%error' => $e->getMessage())), 'error');
|
||||
drupal_set_message(t('The feed from %site seems to be broken because of error "%error".', ['%site' => $feed->label(), '%error' => $e->getMessage()]), 'error');
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -42,10 +42,10 @@ class DefaultParser implements ParserInterface {
|
|||
$feed->setImage($image['uri']);
|
||||
}
|
||||
// Initialize items array.
|
||||
$feed->items = array();
|
||||
$feed->items = [];
|
||||
foreach ($channel as $item) {
|
||||
// Reset the parsed item.
|
||||
$parsed_item = array();
|
||||
$parsed_item = [];
|
||||
// Move the values to an array as expected by processors.
|
||||
$parsed_item['title'] = $item->getTitle();
|
||||
$parsed_item['guid'] = $item->getId();
|
||||
|
|
|
@ -10,7 +10,6 @@ use Drupal\aggregator\FeedInterface;
|
|||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Datetime\DateFormatterInterface;
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Form\ConfigFormBaseTrait;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
|
@ -39,13 +38,6 @@ class DefaultProcessor extends AggregatorPluginSettingsBase implements Processor
|
|||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The entity query object for feed items.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryInterface
|
||||
*/
|
||||
protected $itemQuery;
|
||||
|
||||
/**
|
||||
* The entity storage for items.
|
||||
*
|
||||
|
@ -71,17 +63,14 @@ class DefaultProcessor extends AggregatorPluginSettingsBase implements Processor
|
|||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
|
||||
* The configuration factory object.
|
||||
* @param \Drupal\Core\Entity\Query\QueryInterface $item_query
|
||||
* The entity query object for feed items.
|
||||
* @param \Drupal\aggregator\ItemStorageInterface $item_storage
|
||||
* The entity storage for feed items.
|
||||
* @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
|
||||
* The date formatter service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config, QueryInterface $item_query, ItemStorageInterface $item_storage, DateFormatterInterface $date_formatter) {
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $config, ItemStorageInterface $item_storage, DateFormatterInterface $date_formatter) {
|
||||
$this->configFactory = $config;
|
||||
$this->itemStorage = $item_storage;
|
||||
$this->itemQuery = $item_query;
|
||||
$this->dateFormatter = $date_formatter;
|
||||
// @todo Refactor aggregator plugins to ConfigEntity so merging
|
||||
// the configuration here is not needed.
|
||||
|
@ -97,8 +86,7 @@ class DefaultProcessor extends AggregatorPluginSettingsBase implements Processor
|
|||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('config.factory'),
|
||||
$container->get('entity.query')->get('aggregator_item'),
|
||||
$container->get('entity.manager')->getStorage('aggregator_item'),
|
||||
$container->get('entity_type.manager')->getStorage('aggregator_item'),
|
||||
$container->get('date.formatter')
|
||||
);
|
||||
}
|
||||
|
@ -117,53 +105,53 @@ class DefaultProcessor extends AggregatorPluginSettingsBase implements Processor
|
|||
$config = $this->config('aggregator.settings');
|
||||
$processors = $config->get('processors');
|
||||
$info = $this->getPluginDefinition();
|
||||
$counts = array(3, 5, 10, 15, 20, 25);
|
||||
$counts = [3, 5, 10, 15, 20, 25];
|
||||
$items = array_map(function ($count) {
|
||||
return $this->formatPlural($count, '1 item', '@count items');
|
||||
}, array_combine($counts, $counts));
|
||||
$intervals = array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800);
|
||||
$period = array_map(array($this->dateFormatter, 'formatInterval'), array_combine($intervals, $intervals));
|
||||
$intervals = [3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800];
|
||||
$period = array_map([$this->dateFormatter, 'formatInterval'], array_combine($intervals, $intervals));
|
||||
$period[AGGREGATOR_CLEAR_NEVER] = t('Never');
|
||||
|
||||
$form['processors'][$info['id']] = array();
|
||||
$form['processors'][$info['id']] = [];
|
||||
// Only wrap into details if there is a basic configuration.
|
||||
if (isset($form['basic_conf'])) {
|
||||
$form['processors'][$info['id']] = array(
|
||||
$form['processors'][$info['id']] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Default processor settings'),
|
||||
'#description' => $info['description'],
|
||||
'#open' => in_array($info['id'], $processors),
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
$form['processors'][$info['id']]['aggregator_summary_items'] = array(
|
||||
$form['processors'][$info['id']]['aggregator_summary_items'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('Number of items shown in listing pages'),
|
||||
'#default_value' => $config->get('source.list_max'),
|
||||
'#empty_value' => 0,
|
||||
'#options' => $items,
|
||||
);
|
||||
];
|
||||
|
||||
$form['processors'][$info['id']]['aggregator_clear'] = array(
|
||||
$form['processors'][$info['id']]['aggregator_clear'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('Discard items older than'),
|
||||
'#default_value' => $config->get('items.expire'),
|
||||
'#options' => $period,
|
||||
'#description' => t('Requires a correctly configured <a href=":cron">cron maintenance task</a>.', array(':cron' => $this->url('system.status'))),
|
||||
);
|
||||
'#description' => t('Requires a correctly configured <a href=":cron">cron maintenance task</a>.', [':cron' => $this->url('system.status')]),
|
||||
];
|
||||
|
||||
$lengths = array(0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000);
|
||||
$lengths = [0, 200, 400, 600, 800, 1000, 1200, 1400, 1600, 1800, 2000];
|
||||
$options = array_map(function($length) {
|
||||
return ($length == 0) ? t('Unlimited') : $this->formatPlural($length, '1 character', '@count characters');
|
||||
}, array_combine($lengths, $lengths));
|
||||
|
||||
$form['processors'][$info['id']]['aggregator_teaser_length'] = array(
|
||||
$form['processors'][$info['id']]['aggregator_teaser_length'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('Length of trimmed description'),
|
||||
'#default_value' => $config->get('items.teaser_length'),
|
||||
'#options' => $options,
|
||||
'#description' => t('The maximum number of characters used in the trimmed version of content.'),
|
||||
);
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
@ -197,13 +185,13 @@ class DefaultProcessor extends AggregatorPluginSettingsBase implements Processor
|
|||
// we find a duplicate entry, we resolve it and pass along its ID is such
|
||||
// that we can update it if needed.
|
||||
if (!empty($item['guid'])) {
|
||||
$values = array('fid' => $feed->id(), 'guid' => $item['guid']);
|
||||
$values = ['fid' => $feed->id(), 'guid' => $item['guid']];
|
||||
}
|
||||
elseif ($item['link'] && $item['link'] != $feed->link && $item['link'] != $feed->url) {
|
||||
$values = array('fid' => $feed->id(), 'link' => $item['link']);
|
||||
$values = ['fid' => $feed->id(), 'link' => $item['link']];
|
||||
}
|
||||
else {
|
||||
$values = array('fid' => $feed->id(), 'title' => $item['title']);
|
||||
$values = ['fid' => $feed->id(), 'title' => $item['title']];
|
||||
}
|
||||
|
||||
// Try to load an existing entry.
|
||||
|
@ -211,7 +199,7 @@ class DefaultProcessor extends AggregatorPluginSettingsBase implements Processor
|
|||
$entry = reset($entry);
|
||||
}
|
||||
else {
|
||||
$entry = Item::create(array('langcode' => $feed->language()->getId()));
|
||||
$entry = Item::create(['langcode' => $feed->language()->getId()]);
|
||||
}
|
||||
if ($item['timestamp']) {
|
||||
$entry->setPostedTime($item['timestamp']);
|
||||
|
@ -243,7 +231,7 @@ class DefaultProcessor extends AggregatorPluginSettingsBase implements Processor
|
|||
$this->itemStorage->delete($items);
|
||||
}
|
||||
// @todo This should be moved out to caller with a different message maybe.
|
||||
drupal_set_message(t('The news items from %site have been deleted.', array('%site' => $feed->label())));
|
||||
drupal_set_message(t('The news items from %site have been deleted.', ['%site' => $feed->label()]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -257,7 +245,7 @@ class DefaultProcessor extends AggregatorPluginSettingsBase implements Processor
|
|||
if ($aggregator_clear != AGGREGATOR_CLEAR_NEVER) {
|
||||
// Delete all items that are older than flush item timer.
|
||||
$age = REQUEST_TIME - $aggregator_clear;
|
||||
$result = $this->itemQuery
|
||||
$result = $this->itemStorage->getQuery()
|
||||
->condition('fid', $feed->id())
|
||||
->condition('timestamp', $age, '<')
|
||||
->execute();
|
||||
|
|
|
@ -26,7 +26,7 @@ class AggregatorFeed extends DrupalSqlBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = array(
|
||||
$fields = [
|
||||
'fid' => $this->t('The feed ID.'),
|
||||
'title' => $this->t('Title of the feed.'),
|
||||
'url' => $this->t('URL to the feed.'),
|
||||
|
@ -38,7 +38,7 @@ class AggregatorFeed extends DrupalSqlBase {
|
|||
'etag' => $this->t('Entity tag HTTP response header.'),
|
||||
'modified' => $this->t('When the feed was last modified.'),
|
||||
'block' => $this->t("Number of items to display in the feed's block."),
|
||||
);
|
||||
];
|
||||
if ($this->getModuleSchemaVersion('system') >= 7000) {
|
||||
$fields['queued'] = $this->t('Time when this feed was queued for refresh, 0 if not queued.');
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class AggregatorItem extends DrupalSqlBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return array(
|
||||
return [
|
||||
'iid' => $this->t('Primary Key: Unique ID for feed item.'),
|
||||
'fid' => $this->t('The {aggregator_feed}.fid to which this item belongs.'),
|
||||
'title' => $this->t('Title of the feed item.'),
|
||||
|
@ -36,7 +36,7 @@ class AggregatorItem extends DrupalSqlBase {
|
|||
'description' => $this->t('Body of the feed item.'),
|
||||
'timestamp' => $this->t('Post date of feed item, as a Unix timestamp.'),
|
||||
'guid' => $this->t('Unique identifier for the feed item.'),
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -50,7 +50,7 @@ class Fid extends NumericArgument {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function titleQuery() {
|
||||
$titles = array();
|
||||
$titles = [];
|
||||
|
||||
$feeds = $this->entityManager->getStorage('aggregator_feed')->loadMultiple($this->value);
|
||||
foreach ($feeds as $feed) {
|
||||
|
|
|
@ -50,7 +50,7 @@ class Iid extends NumericArgument {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function titleQuery() {
|
||||
$titles = array();
|
||||
$titles = [];
|
||||
|
||||
$items = $this->entityManager->getStorage('aggregator_item')->loadMultiple($this->value);
|
||||
foreach ($items as $feed) {
|
||||
|
|
|
@ -48,31 +48,31 @@ class Rss extends RssPluginBase {
|
|||
$item->{$name} = $field->value;
|
||||
}
|
||||
|
||||
$item->elements = array(
|
||||
array(
|
||||
$item->elements = [
|
||||
[
|
||||
'key' => 'pubDate',
|
||||
// views_view_row_rss takes care about the escaping.
|
||||
'value' => gmdate('r', $entity->timestamp->value),
|
||||
),
|
||||
array(
|
||||
],
|
||||
[
|
||||
'key' => 'dc:creator',
|
||||
// views_view_row_rss takes care about the escaping.
|
||||
'value' => $entity->author->value,
|
||||
),
|
||||
array(
|
||||
],
|
||||
[
|
||||
'key' => 'guid',
|
||||
// views_view_row_rss takes care about the escaping.
|
||||
'value' => $entity->guid->value,
|
||||
'attributes' => array('isPermaLink' => 'false'),
|
||||
),
|
||||
);
|
||||
'attributes' => ['isPermaLink' => 'false'],
|
||||
],
|
||||
];
|
||||
|
||||
$build = array(
|
||||
$build = [
|
||||
'#theme' => $this->themeFunctions(),
|
||||
'#view' => $this->view,
|
||||
'#options' => $this->options,
|
||||
'#row' => $item,
|
||||
);
|
||||
];
|
||||
return $build;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,8 +39,8 @@ class AddFeedTest extends AggregatorTestBase {
|
|||
'refresh' => '900',
|
||||
];
|
||||
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
|
||||
$this->assertRaw(t('A feed named %feed already exists. Enter a unique title.', array('%feed' => $feed->label())));
|
||||
$this->assertRaw(t('A feed with this URL %url already exists. Enter a unique URL.', array('%url' => $feed->getUrl())));
|
||||
$this->assertRaw(t('A feed named %feed already exists. Enter a unique title.', ['%feed' => $feed->label()]));
|
||||
$this->assertRaw(t('A feed with this URL %url already exists. Enter a unique URL.', ['%url' => $feed->getUrl()]));
|
||||
|
||||
// Delete feed.
|
||||
$this->deleteFeed($feed);
|
||||
|
|
|
@ -22,7 +22,7 @@ class AggregatorAdminTest extends AggregatorTestBase {
|
|||
$this->assertText('Test processor');
|
||||
|
||||
// Set new values and enable test plugins.
|
||||
$edit = array(
|
||||
$edit = [
|
||||
'aggregator_allowed_html_tags' => '<a>',
|
||||
'aggregator_summary_items' => 10,
|
||||
'aggregator_clear' => 3600,
|
||||
|
@ -30,27 +30,27 @@ class AggregatorAdminTest extends AggregatorTestBase {
|
|||
'aggregator_fetcher' => 'aggregator_test_fetcher',
|
||||
'aggregator_parser' => 'aggregator_test_parser',
|
||||
'aggregator_processors[aggregator_test_processor]' => 'aggregator_test_processor',
|
||||
);
|
||||
];
|
||||
$this->drupalPostForm('admin/config/services/aggregator/settings', $edit, t('Save configuration'));
|
||||
$this->assertText(t('The configuration options have been saved.'));
|
||||
|
||||
foreach ($edit as $name => $value) {
|
||||
$this->assertFieldByName($name, $value, format_string('"@name" has correct default value.', array('@name' => $name)));
|
||||
$this->assertFieldByName($name, $value, format_string('"@name" has correct default value.', ['@name' => $name]));
|
||||
}
|
||||
|
||||
// Check for our test processor settings form.
|
||||
$this->assertText(t('Dummy length setting'));
|
||||
// Change its value to ensure that settingsSubmit is called.
|
||||
$edit = array(
|
||||
$edit = [
|
||||
'dummy_length' => 100,
|
||||
);
|
||||
];
|
||||
$this->drupalPostForm('admin/config/services/aggregator/settings', $edit, t('Save configuration'));
|
||||
$this->assertText(t('The configuration options have been saved.'));
|
||||
$this->assertFieldByName('dummy_length', 100, '"dummy_length" has correct default value.');
|
||||
|
||||
// Make sure settings form is still accessible even after uninstalling a module
|
||||
// that provides the selected plugins.
|
||||
$this->container->get('module_installer')->uninstall(array('aggregator_test'));
|
||||
$this->container->get('module_installer')->uninstall(['aggregator_test']);
|
||||
$this->resetAll();
|
||||
$this->drupalGet('admin/config/services/aggregator/settings');
|
||||
$this->assertResponse(200);
|
||||
|
@ -59,7 +59,7 @@ class AggregatorAdminTest extends AggregatorTestBase {
|
|||
/**
|
||||
* Tests the overview page.
|
||||
*/
|
||||
function testOverviewPage() {
|
||||
public function testOverviewPage() {
|
||||
$feed = $this->createFeed($this->getRSS091Sample());
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
|
||||
|
|
|
@ -16,30 +16,30 @@ class AggregatorCronTest extends AggregatorTestBase {
|
|||
$this->createSampleNodes();
|
||||
$feed = $this->createFeed();
|
||||
$this->cronRun();
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
$this->deleteFeedItems($feed);
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
$this->cronRun();
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
|
||||
// Test feed locking when queued for update.
|
||||
$this->deleteFeedItems($feed);
|
||||
db_update('aggregator_feed')
|
||||
->condition('fid', $feed->id())
|
||||
->fields(array(
|
||||
->fields([
|
||||
'queued' => REQUEST_TIME,
|
||||
))
|
||||
])
|
||||
->execute();
|
||||
$this->cronRun();
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
|
||||
$this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
db_update('aggregator_feed')
|
||||
->condition('fid', $feed->id())
|
||||
->fields(array(
|
||||
->fields([
|
||||
'queued' => 0,
|
||||
))
|
||||
])
|
||||
->execute();
|
||||
$this->cronRun();
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField());
|
||||
$this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ class AggregatorRenderingTest extends AggregatorTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('block', 'test_page_test');
|
||||
public static $modules = ['block', 'test_page_test'];
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
@ -35,15 +35,15 @@ class AggregatorRenderingTest extends AggregatorTestBase {
|
|||
$this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
|
||||
|
||||
// Need admin user to be able to access block admin.
|
||||
$admin_user = $this->drupalCreateUser(array(
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'administer blocks',
|
||||
'access administration pages',
|
||||
'administer news feeds',
|
||||
'access news feeds',
|
||||
));
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$block = $this->drupalPlaceBlock("aggregator_feed_block", array('label' => 'feed-' . $feed->label()));
|
||||
$block = $this->drupalPlaceBlock("aggregator_feed_block", ['label' => 'feed-' . $feed->label()]);
|
||||
|
||||
// Configure the feed that should be displayed.
|
||||
$block->getPlugin()->setConfigurationValue('feed', $feed->id());
|
||||
|
@ -56,20 +56,20 @@ class AggregatorRenderingTest extends AggregatorTestBase {
|
|||
|
||||
// Confirm items appear as links.
|
||||
$items = $this->container->get('entity.manager')->getStorage('aggregator_item')->loadByFeed($feed->id(), 1);
|
||||
$links = $this->xpath('//a[@href = :href]', array(':href' => reset($items)->getLink()));
|
||||
$links = $this->xpath('//a[@href = :href]', [':href' => reset($items)->getLink()]);
|
||||
$this->assert(isset($links[0]), 'Item link found.');
|
||||
|
||||
// Find the expected read_more link.
|
||||
$href = $feed->url();
|
||||
$links = $this->xpath('//a[@href = :href]', array(':href' => $href));
|
||||
$this->assert(isset($links[0]), format_string('Link to href %href found.', array('%href' => $href)));
|
||||
$links = $this->xpath('//a[@href = :href]', [':href' => $href]);
|
||||
$this->assert(isset($links[0]), format_string('Link to href %href found.', ['%href' => $href]));
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$cache_tags = explode(' ', $cache_tags_header);
|
||||
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
|
||||
|
||||
// Visit that page.
|
||||
$this->drupalGet($feed->urlInfo()->getInternalPath());
|
||||
$correct_titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => $feed->label()));
|
||||
$correct_titles = $this->xpath('//h1[normalize-space(text())=:title]', [':title' => $feed->label()]);
|
||||
$this->assertFalse(empty($correct_titles), 'Aggregator feed page is available and has the correct title.');
|
||||
$cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
|
||||
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
|
||||
|
@ -103,18 +103,18 @@ class AggregatorRenderingTest extends AggregatorTestBase {
|
|||
|
||||
// Check for presence of an aggregator pager.
|
||||
$this->drupalGet('aggregator');
|
||||
$elements = $this->xpath("//ul[contains(@class, :class)]", array(':class' => 'pager__items'));
|
||||
$elements = $this->xpath("//ul[contains(@class, :class)]", [':class' => 'pager__items']);
|
||||
$this->assertTrue(!empty($elements), 'Individual source page contains a pager.');
|
||||
|
||||
// Check for sources page title.
|
||||
$this->drupalGet('aggregator/sources');
|
||||
$titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => 'Sources'));
|
||||
$titles = $this->xpath('//h1[normalize-space(text())=:title]', [':title' => 'Sources']);
|
||||
$this->assertTrue(!empty($titles), 'Source page contains correct title.');
|
||||
|
||||
// Find the expected read_more link on the sources page.
|
||||
$href = $feed->url();
|
||||
$links = $this->xpath('//a[@href = :href]', array(':href' => $href));
|
||||
$this->assertTrue(isset($links[0]), SafeMarkup::format('Link to href %href found.', array('%href' => $href)));
|
||||
$links = $this->xpath('//a[@href = :href]', [':href' => $href]);
|
||||
$this->assertTrue(isset($links[0]), SafeMarkup::format('Link to href %href found.', ['%href' => $href]));
|
||||
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
|
||||
$cache_tags = explode(' ', $cache_tags_header);
|
||||
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
|
||||
|
@ -139,7 +139,7 @@ class AggregatorRenderingTest extends AggregatorTestBase {
|
|||
|
||||
// Check for the presence of a pager.
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$elements = $this->xpath("//ul[contains(@class, :class)]", array(':class' => 'pager__items'));
|
||||
$elements = $this->xpath("//ul[contains(@class, :class)]", [':class' => 'pager__items']);
|
||||
$this->assertTrue(!empty($elements), 'Individual source page contains a pager.');
|
||||
$cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags'));
|
||||
$this->assertTrue(in_array('aggregator_feed:' . $feed->id(), $cache_tags));
|
||||
|
|
|
@ -9,6 +9,9 @@ use Drupal\aggregator\FeedInterface;
|
|||
|
||||
/**
|
||||
* Defines a base class for testing the Aggregator module.
|
||||
*
|
||||
* @deprecated Scheduled for removal in Drupal 9.0.0.
|
||||
* Use \Drupal\Tests\aggregator\Functional\AggregatorTestBase instead.
|
||||
*/
|
||||
abstract class AggregatorTestBase extends WebTestBase {
|
||||
|
||||
|
@ -34,10 +37,10 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
|
||||
// Create an Article node type.
|
||||
if ($this->profile != 'standard') {
|
||||
$this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
}
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer news feeds', 'access news feeds', 'create article content'));
|
||||
$this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer news feeds', 'access news feeds', 'create article content']);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
}
|
||||
|
@ -58,16 +61,16 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
*
|
||||
* @see getFeedEditArray()
|
||||
*/
|
||||
public function createFeed($feed_url = NULL, array $edit = array()) {
|
||||
public function createFeed($feed_url = NULL, array $edit = []) {
|
||||
$edit = $this->getFeedEditArray($feed_url, $edit);
|
||||
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
|
||||
$this->assertText(t('The feed @name has been added.', array('@name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));
|
||||
$this->assertText(t('The feed @name has been added.', ['@name' => $edit['title[0][value]']]), format_string('The feed @name has been added.', ['@name' => $edit['title[0][value]']]));
|
||||
|
||||
// Verify that the creation message contains a link to a feed.
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'aggregator/sources/']);
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title[0][value]'], ':url' => $edit['url[0][value]']))->fetchField();
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $edit['title[0][value]'], ':url' => $edit['url[0][value]']])->fetchField();
|
||||
$this->assertTrue(!empty($fid), 'The feed found in database.');
|
||||
return Feed::load($fid);
|
||||
}
|
||||
|
@ -79,8 +82,8 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
* Feed object representing the feed.
|
||||
*/
|
||||
public function deleteFeed(FeedInterface $feed) {
|
||||
$this->drupalPostForm('aggregator/sources/' . $feed->id() . '/delete', array(), t('Delete'));
|
||||
$this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->label())), 'Feed deleted successfully.');
|
||||
$this->drupalPostForm('aggregator/sources/' . $feed->id() . '/delete', [], t('Delete'));
|
||||
$this->assertRaw(t('The feed %title has been deleted.', ['%title' => $feed->label()]), 'Feed deleted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,19 +98,19 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
* @return array
|
||||
* A feed array.
|
||||
*/
|
||||
public function getFeedEditArray($feed_url = NULL, array $edit = array()) {
|
||||
public function getFeedEditArray($feed_url = NULL, array $edit = []) {
|
||||
$feed_name = $this->randomMachineName(10);
|
||||
if (!$feed_url) {
|
||||
$feed_url = \Drupal::url('view.frontpage.feed_1', array(), array(
|
||||
'query' => array('feed' => $feed_name),
|
||||
$feed_url = \Drupal::url('view.frontpage.feed_1', [], [
|
||||
'query' => ['feed' => $feed_name],
|
||||
'absolute' => TRUE,
|
||||
));
|
||||
]);
|
||||
}
|
||||
$edit += array(
|
||||
$edit += [
|
||||
'title[0][value]' => $feed_name,
|
||||
'url[0][value]' => $feed_url,
|
||||
'refresh' => '900',
|
||||
);
|
||||
];
|
||||
return $edit;
|
||||
}
|
||||
|
||||
|
@ -123,19 +126,19 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
* @return \Drupal\aggregator\FeedInterface
|
||||
* A feed object.
|
||||
*/
|
||||
public function getFeedEditObject($feed_url = NULL, array $values = array()) {
|
||||
public function getFeedEditObject($feed_url = NULL, array $values = []) {
|
||||
$feed_name = $this->randomMachineName(10);
|
||||
if (!$feed_url) {
|
||||
$feed_url = \Drupal::url('view.frontpage.feed_1', array(
|
||||
'query' => array('feed' => $feed_name),
|
||||
$feed_url = \Drupal::url('view.frontpage.feed_1', [
|
||||
'query' => ['feed' => $feed_name],
|
||||
'absolute' => TRUE,
|
||||
));
|
||||
]);
|
||||
}
|
||||
$values += array(
|
||||
$values += [
|
||||
'title' => $feed_name,
|
||||
'url' => $feed_url,
|
||||
'refresh' => '900',
|
||||
);
|
||||
];
|
||||
return Feed::create($values);
|
||||
}
|
||||
|
||||
|
@ -165,7 +168,7 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
public function updateFeedItems(FeedInterface $feed, $expected_count = NULL) {
|
||||
// First, let's ensure we can get to the rss xml.
|
||||
$this->drupalGet($feed->getUrl());
|
||||
$this->assertResponse(200, format_string(':url is reachable.', array(':url' => $feed->getUrl())));
|
||||
$this->assertResponse(200, format_string(':url is reachable.', [':url' => $feed->getUrl()]));
|
||||
|
||||
// Attempt to access the update link directly without an access token.
|
||||
$this->drupalGet('admin/config/services/aggregator/update/' . $feed->id());
|
||||
|
@ -176,15 +179,15 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
$this->clickLink('Update items');
|
||||
|
||||
// Ensure we have the right number of items.
|
||||
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()));
|
||||
$feed->items = array();
|
||||
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()]);
|
||||
$feed->items = [];
|
||||
foreach ($result as $item) {
|
||||
$feed->items[] = $item->iid;
|
||||
}
|
||||
|
||||
if ($expected_count !== NULL) {
|
||||
$feed->item_count = count($feed->items);
|
||||
$this->assertEqual($expected_count, $feed->item_count, format_string('Total items in feed equal to the total items in database (@val1 != @val2)', array('@val1' => $expected_count, '@val2' => $feed->item_count)));
|
||||
$this->assertEqual($expected_count, $feed->item_count, format_string('Total items in feed equal to the total items in database (@val1 != @val2)', ['@val1' => $expected_count, '@val2' => $feed->item_count]));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,8 +198,8 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
* Feed object representing the feed.
|
||||
*/
|
||||
public function deleteFeedItems(FeedInterface $feed) {
|
||||
$this->drupalPostForm('admin/config/services/aggregator/delete/' . $feed->id(), array(), t('Delete items'));
|
||||
$this->assertRaw(t('The news items from %title have been deleted.', array('%title' => $feed->label())), 'Feed items deleted.');
|
||||
$this->drupalPostForm('admin/config/services/aggregator/delete/' . $feed->id(), [], t('Delete items'));
|
||||
$this->assertRaw(t('The news items from %title have been deleted.', ['%title' => $feed->label()]), 'Feed items deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,10 +212,10 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
*/
|
||||
public function updateAndDelete(FeedInterface $feed, $expected_count) {
|
||||
$this->updateFeedItems($feed, $expected_count);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$this->assertTrue($count);
|
||||
$this->deleteFeedItems($feed);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$this->assertTrue($count == 0);
|
||||
}
|
||||
|
||||
|
@ -228,7 +231,7 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
* TRUE if feed is unique.
|
||||
*/
|
||||
public function uniqueFeed($feed_name, $feed_url) {
|
||||
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed_name, ':url' => $feed_url))->fetchField();
|
||||
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $feed_name, ':url' => $feed_url])->fetchField();
|
||||
return (1 == $result);
|
||||
}
|
||||
|
||||
|
@ -271,7 +274,8 @@ abstract class AggregatorTestBase extends WebTestBase {
|
|||
EOF;
|
||||
|
||||
$path = 'public://valid-opml.xml';
|
||||
return file_unmanaged_save_data($opml, $path);
|
||||
// Add the UTF-8 byte order mark.
|
||||
return file_unmanaged_save_data(chr(239) . chr(187) . chr(191) . $opml, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -354,7 +358,7 @@ EOF;
|
|||
public function createSampleNodes($count = 5) {
|
||||
// Post $count article nodes.
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit['body[0][value]'] = $this->randomMachineName();
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save'));
|
||||
|
@ -368,10 +372,10 @@ EOF;
|
|||
$this->config('aggregator.settings')
|
||||
->set('fetcher', 'aggregator_test_fetcher')
|
||||
->set('parser', 'aggregator_test_parser')
|
||||
->set('processors', array(
|
||||
->set('processors', [
|
||||
'aggregator_test_processor' => 'aggregator_test_processor',
|
||||
'aggregator' => 'aggregator',
|
||||
))
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ class FeedAdminDisplayTest extends AggregatorTestBase {
|
|||
*/
|
||||
public function testFeedUpdateFields() {
|
||||
// Create scheduled feed.
|
||||
$scheduled_feed = $this->createFeed(NULL, array('refresh' => '900'));
|
||||
$scheduled_feed = $this->createFeed(NULL, ['refresh' => '900']);
|
||||
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
$this->assertResponse(200, 'Aggregator feed overview page exists.');
|
||||
|
@ -40,7 +40,7 @@ class FeedAdminDisplayTest extends AggregatorTestBase {
|
|||
$this->deleteFeed($scheduled_feed);
|
||||
|
||||
// Create non-scheduled feed.
|
||||
$non_scheduled_feed = $this->createFeed(NULL, array('refresh' => '0'));
|
||||
$non_scheduled_feed = $this->createFeed(NULL, ['refresh' => '0']);
|
||||
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
// The non scheduled feed shows that it has not been updated yet.
|
||||
|
|
|
@ -16,14 +16,14 @@ class FeedLanguageTest extends AggregatorTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('language');
|
||||
public static $modules = ['language'];
|
||||
|
||||
/**
|
||||
* List of langcodes.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $langcodes = array();
|
||||
protected $langcodes = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -32,12 +32,12 @@ class FeedLanguageTest extends AggregatorTestBase {
|
|||
parent::setUp();
|
||||
|
||||
// Create test languages.
|
||||
$this->langcodes = array(ConfigurableLanguage::load('en'));
|
||||
$this->langcodes = [ConfigurableLanguage::load('en')];
|
||||
for ($i = 1; $i < 3; ++$i) {
|
||||
$language = ConfigurableLanguage::create(array(
|
||||
$language = ConfigurableLanguage::create([
|
||||
'id' => 'l' . $i,
|
||||
'label' => $this->randomString(),
|
||||
));
|
||||
]);
|
||||
$language->save();
|
||||
$this->langcodes[$i] = $language->id();
|
||||
}
|
||||
|
@ -57,10 +57,10 @@ class FeedLanguageTest extends AggregatorTestBase {
|
|||
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
|
||||
|
||||
/** @var \Drupal\aggregator\FeedInterface[] $feeds */
|
||||
$feeds = array();
|
||||
$feeds = [];
|
||||
// Create feeds.
|
||||
$feeds[1] = $this->createFeed(NULL, array('langcode[0][value]' => $this->langcodes[1]));
|
||||
$feeds[2] = $this->createFeed(NULL, array('langcode[0][value]' => $this->langcodes[2]));
|
||||
$feeds[1] = $this->createFeed(NULL, ['langcode[0][value]' => $this->langcodes[1]]);
|
||||
$feeds[2] = $this->createFeed(NULL, ['langcode[0][value]' => $this->langcodes[2]]);
|
||||
|
||||
// Make sure that the language has been assigned.
|
||||
$this->assertEqual($feeds[1]->language()->getId(), $this->langcodes[1]);
|
||||
|
@ -74,7 +74,7 @@ class FeedLanguageTest extends AggregatorTestBase {
|
|||
// the one from the feed.
|
||||
foreach ($feeds as $feed) {
|
||||
/** @var \Drupal\aggregator\ItemInterface[] $items */
|
||||
$items = entity_load_multiple_by_properties('aggregator_item', array('fid' => $feed->id()));
|
||||
$items = entity_load_multiple_by_properties('aggregator_item', ['fid' => $feed->id()]);
|
||||
$this->assertTrue(count($items) > 0, 'Feed items were created.');
|
||||
foreach ($items as $item) {
|
||||
$this->assertEqual($item->language()->getId(), $feed->language()->getId());
|
||||
|
|
|
@ -26,47 +26,47 @@ class UpdateFeedItemTest extends AggregatorTestBase {
|
|||
$this->deleteFeed($feed);
|
||||
|
||||
// Test updating feed items without valid timestamp information.
|
||||
$edit = array(
|
||||
$edit = [
|
||||
'title[0][value]' => "Feed without publish timestamp",
|
||||
'url[0][value]' => $this->getRSS091Sample(),
|
||||
);
|
||||
];
|
||||
|
||||
$this->drupalGet($edit['url[0][value]']);
|
||||
$this->assertResponse(200);
|
||||
|
||||
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
|
||||
$this->assertText(t('The feed @name has been added.', array('@name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));
|
||||
$this->assertText(t('The feed @name has been added.', ['@name' => $edit['title[0][value]']]), format_string('The feed @name has been added.', ['@name' => $edit['title[0][value]']]));
|
||||
|
||||
// Verify that the creation message contains a link to a feed.
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'aggregator/sources/']);
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url[0][value]']))->fetchField();
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE url = :url", [':url' => $edit['url[0][value]']])->fetchField();
|
||||
$feed = Feed::load($fid);
|
||||
|
||||
$feed->refreshItems();
|
||||
$before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
|
||||
$before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
|
||||
// Sleep for 3 second.
|
||||
sleep(3);
|
||||
db_update('aggregator_feed')
|
||||
->condition('fid', $feed->id())
|
||||
->fields(array(
|
||||
->fields([
|
||||
'checked' => 0,
|
||||
'hash' => '',
|
||||
'etag' => '',
|
||||
'modified' => 0,
|
||||
))
|
||||
])
|
||||
->execute();
|
||||
$feed->refreshItems();
|
||||
|
||||
$after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
|
||||
$this->assertTrue($before === $after, format_string('Publish timestamp of feed item was not updated (@before === @after)', array('@before' => $before, '@after' => $after)));
|
||||
$after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$this->assertTrue($before === $after, format_string('Publish timestamp of feed item was not updated (@before === @after)', ['@before' => $before, '@after' => $after]));
|
||||
|
||||
// Make sure updating items works even after uninstalling a module
|
||||
// that provides the selected plugins.
|
||||
$this->enableTestPlugins();
|
||||
$this->container->get('module_installer')->uninstall(array('aggregator_test'));
|
||||
$this->container->get('module_installer')->uninstall(['aggregator_test']);
|
||||
$this->updateFeedItems($feed);
|
||||
$this->assertResponse(200);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ class UpdateFeedTest extends AggregatorTestBase {
|
|||
* Creates a feed and attempts to update it.
|
||||
*/
|
||||
public function testUpdateFeed() {
|
||||
$remaining_fields = array('title[0][value]', 'url[0][value]', '');
|
||||
$remaining_fields = ['title[0][value]', 'url[0][value]', ''];
|
||||
foreach ($remaining_fields as $same_field) {
|
||||
$feed = $this->createFeed();
|
||||
|
||||
|
@ -24,10 +24,10 @@ class UpdateFeedTest extends AggregatorTestBase {
|
|||
$edit[$same_field] = $feed->{$same_field}->value;
|
||||
}
|
||||
$this->drupalPostForm('aggregator/sources/' . $feed->id() . '/configure', $edit, t('Save'));
|
||||
$this->assertText(t('The feed @name has been updated.', array('@name' => $edit['title[0][value]'])), format_string('The feed %name has been updated.', array('%name' => $edit['title[0][value]'])));
|
||||
$this->assertText(t('The feed @name has been updated.', ['@name' => $edit['title[0][value]']]), format_string('The feed %name has been updated.', ['%name' => $edit['title[0][value]']]));
|
||||
|
||||
// Verify that the creation message contains a link to a feed.
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'aggregator/sources/']);
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
// Check feed data.
|
||||
|
|
|
@ -75,20 +75,20 @@ class TestProcessor extends AggregatorPluginSettingsBase implements ProcessorInt
|
|||
$processors = $this->config('aggregator.settings')->get('processors');
|
||||
$info = $this->getPluginDefinition();
|
||||
|
||||
$form['processors'][$info['id']] = array(
|
||||
$form['processors'][$info['id']] = [
|
||||
'#type' => 'details',
|
||||
'#title' => t('Test processor settings'),
|
||||
'#description' => $info['description'],
|
||||
'#open' => in_array($info['id'], $processors),
|
||||
);
|
||||
];
|
||||
// Add some dummy settings to verify settingsForm is called.
|
||||
$form['processors'][$info['id']]['dummy_length'] = array(
|
||||
$form['processors'][$info['id']]['dummy_length'] = [
|
||||
'#title' => t('Dummy length setting'),
|
||||
'#type' => 'number',
|
||||
'#min' => 1,
|
||||
'#max' => 1000,
|
||||
'#default_value' => $this->configuration['items']['dummy_length'],
|
||||
);
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,379 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\aggregator\FeedInterface;
|
||||
|
||||
/**
|
||||
* Defines a base class for testing the Aggregator module.
|
||||
*/
|
||||
abstract class AggregatorTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* A user with permission to administer feeds and create content.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['block', 'node', 'aggregator', 'aggregator_test', 'views'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create an Article node type.
|
||||
if ($this->profile != 'standard') {
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
}
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer news feeds', 'access news feeds', 'create article content']);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an aggregator feed.
|
||||
*
|
||||
* This method simulates the form submission on path aggregator/sources/add.
|
||||
*
|
||||
* @param string $feed_url
|
||||
* (optional) If given, feed will be created with this URL, otherwise
|
||||
* /rss.xml will be used. Defaults to NULL.
|
||||
* @param array $edit
|
||||
* Array with additional form fields.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* Full feed object if possible.
|
||||
*
|
||||
* @see getFeedEditArray()
|
||||
*/
|
||||
public function createFeed($feed_url = NULL, array $edit = []) {
|
||||
$edit = $this->getFeedEditArray($feed_url, $edit);
|
||||
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
|
||||
$this->assertText(t('The feed @name has been added.', ['@name' => $edit['title[0][value]']]), format_string('The feed @name has been added.', ['@name' => $edit['title[0][value]']]));
|
||||
|
||||
// Verify that the creation message contains a link to a feed.
|
||||
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'aggregator/sources/']);
|
||||
$this->assert(isset($view_link), 'The message area contains a link to a feed');
|
||||
|
||||
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $edit['title[0][value]'], ':url' => $edit['url[0][value]']])->fetchField();
|
||||
$this->assertTrue(!empty($fid), 'The feed found in database.');
|
||||
return Feed::load($fid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an aggregator feed.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Feed object representing the feed.
|
||||
*/
|
||||
public function deleteFeed(FeedInterface $feed) {
|
||||
$this->drupalPostForm('aggregator/sources/' . $feed->id() . '/delete', [], t('Delete'));
|
||||
$this->assertRaw(t('The feed %title has been deleted.', ['%title' => $feed->label()]), 'Feed deleted successfully.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a randomly generated feed edit array.
|
||||
*
|
||||
* @param string $feed_url
|
||||
* (optional) If given, feed will be created with this URL, otherwise
|
||||
* /rss.xml will be used. Defaults to NULL.
|
||||
* @param array $edit
|
||||
* Array with additional form fields.
|
||||
*
|
||||
* @return array
|
||||
* A feed array.
|
||||
*/
|
||||
public function getFeedEditArray($feed_url = NULL, array $edit = []) {
|
||||
$feed_name = $this->randomMachineName(10);
|
||||
if (!$feed_url) {
|
||||
$feed_url = \Drupal::url('view.frontpage.feed_1', [], [
|
||||
'query' => ['feed' => $feed_name],
|
||||
'absolute' => TRUE,
|
||||
]);
|
||||
}
|
||||
$edit += [
|
||||
'title[0][value]' => $feed_name,
|
||||
'url[0][value]' => $feed_url,
|
||||
'refresh' => '900',
|
||||
];
|
||||
return $edit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a randomly generated feed edit object.
|
||||
*
|
||||
* @param string $feed_url
|
||||
* (optional) If given, feed will be created with this URL, otherwise
|
||||
* /rss.xml will be used. Defaults to NULL.
|
||||
* @param array $values
|
||||
* (optional) Default values to initialize object properties with.
|
||||
*
|
||||
* @return \Drupal\aggregator\FeedInterface
|
||||
* A feed object.
|
||||
*/
|
||||
public function getFeedEditObject($feed_url = NULL, array $values = []) {
|
||||
$feed_name = $this->randomMachineName(10);
|
||||
if (!$feed_url) {
|
||||
$feed_url = \Drupal::url('view.frontpage.feed_1', [
|
||||
'query' => ['feed' => $feed_name],
|
||||
'absolute' => TRUE,
|
||||
]);
|
||||
}
|
||||
$values += [
|
||||
'title' => $feed_name,
|
||||
'url' => $feed_url,
|
||||
'refresh' => '900',
|
||||
];
|
||||
return Feed::create($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of the randomly created feed array.
|
||||
*
|
||||
* @return int
|
||||
* Number of feed items on default feed created by createFeed().
|
||||
*/
|
||||
public function getDefaultFeedItemCount() {
|
||||
// Our tests are based off of rss.xml, so let's find out how many elements should be related.
|
||||
$feed_count = db_query_range('SELECT COUNT(DISTINCT nid) FROM {node_field_data} n WHERE n.promote = 1 AND n.status = 1', 0, $this->config('system.rss')->get('items.limit'))->fetchField();
|
||||
return $feed_count > 10 ? 10 : $feed_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the feed items.
|
||||
*
|
||||
* This method simulates a click to
|
||||
* admin/config/services/aggregator/update/$fid.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Feed object representing the feed.
|
||||
* @param int|null $expected_count
|
||||
* Expected number of feed items. If omitted no check will happen.
|
||||
*/
|
||||
public function updateFeedItems(FeedInterface $feed, $expected_count = NULL) {
|
||||
// First, let's ensure we can get to the rss xml.
|
||||
$this->drupalGet($feed->getUrl());
|
||||
$this->assertResponse(200, format_string(':url is reachable.', [':url' => $feed->getUrl()]));
|
||||
|
||||
// Attempt to access the update link directly without an access token.
|
||||
$this->drupalGet('admin/config/services/aggregator/update/' . $feed->id());
|
||||
$this->assertResponse(403);
|
||||
|
||||
// Refresh the feed (simulated link click).
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
$this->clickLink('Update items');
|
||||
|
||||
// Ensure we have the right number of items.
|
||||
$result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()]);
|
||||
$feed->items = [];
|
||||
foreach ($result as $item) {
|
||||
$feed->items[] = $item->iid;
|
||||
}
|
||||
|
||||
if ($expected_count !== NULL) {
|
||||
$feed->item_count = count($feed->items);
|
||||
$this->assertEqual($expected_count, $feed->item_count, format_string('Total items in feed equal to the total items in database (@val1 != @val2)', ['@val1' => $expected_count, '@val2' => $feed->item_count]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms an item removal from a feed.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Feed object representing the feed.
|
||||
*/
|
||||
public function deleteFeedItems(FeedInterface $feed) {
|
||||
$this->drupalPostForm('admin/config/services/aggregator/delete/' . $feed->id(), [], t('Delete items'));
|
||||
$this->assertRaw(t('The news items from %title have been deleted.', ['%title' => $feed->label()]), 'Feed items deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds and deletes feed items and ensure that the count is zero.
|
||||
*
|
||||
* @param \Drupal\aggregator\FeedInterface $feed
|
||||
* Feed object representing the feed.
|
||||
* @param int $expected_count
|
||||
* Expected number of feed items.
|
||||
*/
|
||||
public function updateAndDelete(FeedInterface $feed, $expected_count) {
|
||||
$this->updateFeedItems($feed, $expected_count);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$this->assertTrue($count);
|
||||
$this->deleteFeedItems($feed);
|
||||
$count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', [':fid' => $feed->id()])->fetchField();
|
||||
$this->assertTrue($count == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the feed name and URL are unique.
|
||||
*
|
||||
* @param string $feed_name
|
||||
* String containing the feed name to check.
|
||||
* @param string $feed_url
|
||||
* String containing the feed url to check.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if feed is unique.
|
||||
*/
|
||||
public function uniqueFeed($feed_name, $feed_url) {
|
||||
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $feed_name, ':url' => $feed_url])->fetchField();
|
||||
return (1 == $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid OPML file from an array of feeds.
|
||||
*
|
||||
* @param array $feeds
|
||||
* An array of feeds.
|
||||
*
|
||||
* @return string
|
||||
* Path to valid OPML file.
|
||||
*/
|
||||
public function getValidOpml(array $feeds) {
|
||||
// Properly escape URLs so that XML parsers don't choke on them.
|
||||
foreach ($feeds as &$feed) {
|
||||
$feed['url[0][value]'] = Html::escape($feed['url[0][value]']);
|
||||
}
|
||||
/**
|
||||
* Does not have an XML declaration, must pass the parser.
|
||||
*/
|
||||
$opml = <<<EOF
|
||||
<opml version="1.0">
|
||||
<head></head>
|
||||
<body>
|
||||
<!-- First feed to be imported. -->
|
||||
<outline text="{$feeds[0]['title[0][value]']}" xmlurl="{$feeds[0]['url[0][value]']}" />
|
||||
|
||||
<!-- Second feed. Test string delimitation and attribute order. -->
|
||||
<outline xmlurl='{$feeds[1]['url[0][value]']}' text='{$feeds[1]['title[0][value]']}'/>
|
||||
|
||||
<!-- Test for duplicate URL and title. -->
|
||||
<outline xmlurl="{$feeds[0]['url[0][value]']}" text="Duplicate URL"/>
|
||||
<outline xmlurl="http://duplicate.title" text="{$feeds[1]['title[0][value]']}"/>
|
||||
|
||||
<!-- Test that feeds are only added with required attributes. -->
|
||||
<outline text="{$feeds[2]['title[0][value]']}" />
|
||||
<outline xmlurl="{$feeds[2]['url[0][value]']}" />
|
||||
</body>
|
||||
</opml>
|
||||
EOF;
|
||||
|
||||
$path = 'public://valid-opml.xml';
|
||||
// Add the UTF-8 byte order mark.
|
||||
return file_unmanaged_save_data(chr(239) . chr(187) . chr(191) . $opml, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an invalid OPML file.
|
||||
*
|
||||
* @return string
|
||||
* Path to invalid OPML file.
|
||||
*/
|
||||
public function getInvalidOpml() {
|
||||
$opml = <<<EOF
|
||||
<opml>
|
||||
<invalid>
|
||||
</opml>
|
||||
EOF;
|
||||
|
||||
$path = 'public://invalid-opml.xml';
|
||||
return file_unmanaged_save_data($opml, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid but empty OPML file.
|
||||
*
|
||||
* @return string
|
||||
* Path to empty OPML file.
|
||||
*/
|
||||
public function getEmptyOpml() {
|
||||
$opml = <<<EOF
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<opml version="1.0">
|
||||
<head></head>
|
||||
<body>
|
||||
<outline text="Sample text" />
|
||||
<outline text="Sample text" url="Sample URL" />
|
||||
</body>
|
||||
</opml>
|
||||
EOF;
|
||||
|
||||
$path = 'public://empty-opml.xml';
|
||||
return file_unmanaged_save_data($opml, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a example RSS091 feed.
|
||||
*
|
||||
* @return string
|
||||
* Path to the feed.
|
||||
*/
|
||||
public function getRSS091Sample() {
|
||||
return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_rss091.xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a example Atom feed.
|
||||
*
|
||||
* @return string
|
||||
* Path to the feed.
|
||||
*/
|
||||
public function getAtomSample() {
|
||||
// The content of this sample ATOM feed is based directly off of the
|
||||
// example provided in RFC 4287.
|
||||
return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_atom.xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a example feed.
|
||||
*
|
||||
* @return string
|
||||
* Path to the feed.
|
||||
*/
|
||||
public function getHtmlEntitiesSample() {
|
||||
return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_title_entities.xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates sample article nodes.
|
||||
*
|
||||
* @param int $count
|
||||
* (optional) The number of nodes to generate. Defaults to five.
|
||||
*/
|
||||
public function createSampleNodes($count = 5) {
|
||||
// Post $count article nodes.
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$edit = [];
|
||||
$edit['title[0][value]'] = $this->randomMachineName();
|
||||
$edit['body[0][value]'] = $this->randomMachineName();
|
||||
$this->drupalPostForm('node/add/article', $edit, t('Save'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the plugins coming with aggregator_test module.
|
||||
*/
|
||||
public function enableTestPlugins() {
|
||||
$this->config('aggregator.settings')
|
||||
->set('fetcher', 'aggregator_test_fetcher')
|
||||
->set('parser', 'aggregator_test_parser')
|
||||
->set('processors', [
|
||||
'aggregator_test_processor' => 'aggregator_test_processor',
|
||||
'aggregator' => 'aggregator',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
/**
|
||||
* Delete feed items from a feed.
|
||||
|
@ -13,15 +13,15 @@ class DeleteFeedItemTest extends AggregatorTestBase {
|
|||
*/
|
||||
public function testDeleteFeedItem() {
|
||||
// Create a bunch of test feeds.
|
||||
$feed_urls = array();
|
||||
$feed_urls = [];
|
||||
// No last-modified, no etag.
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', array(), array('absolute' => TRUE));
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', [], ['absolute' => TRUE]);
|
||||
// Last-modified, but no etag.
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', array('use_last_modified' => 1), array('absolute' => TRUE));
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', ['use_last_modified' => 1], ['absolute' => TRUE]);
|
||||
// No Last-modified, but etag.
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', array('use_last_modified' => 0, 'use_etag' => 1), array('absolute' => TRUE));
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', ['use_last_modified' => 0, 'use_etag' => 1], ['absolute' => TRUE]);
|
||||
// Last-modified and etag.
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', array('use_last_modified' => 1, 'use_etag' => 1), array('absolute' => TRUE));
|
||||
$feed_urls[] = \Drupal::url('aggregator_test.feed', ['use_last_modified' => 1, 'use_etag' => 1], ['absolute' => TRUE]);
|
||||
|
||||
foreach ($feed_urls as $feed_url) {
|
||||
$feed = $this->createFeed($feed_url);
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
/**
|
||||
* Delete feed test.
|
||||
|
@ -14,7 +14,7 @@ class DeleteFeedTest extends AggregatorTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('block');
|
||||
public static $modules = ['block'];
|
||||
|
||||
/**
|
||||
* Deletes a feed and ensures that all of its services are deleted.
|
||||
|
@ -43,7 +43,7 @@ class DeleteFeedTest extends AggregatorTestBase {
|
|||
$this->assertResponse(404, 'Deleted feed source does not exists.');
|
||||
|
||||
// Check database for feed.
|
||||
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed1->label(), ':url' => $feed1->getUrl()))->fetchField();
|
||||
$result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", [':title' => $feed1->label(), ':url' => $feed1->getUrl()])->fetchField();
|
||||
$this->assertFalse($result, 'Feed not found in database');
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
|
||||
|
@ -17,7 +17,7 @@ class FeedCacheTagsTest extends EntityWithUriCacheTagsTestBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('aggregator');
|
||||
public static $modules = ['aggregator'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -37,13 +37,13 @@ class FeedCacheTagsTest extends EntityWithUriCacheTagsTestBase {
|
|||
*/
|
||||
protected function createEntity() {
|
||||
// Create a "Llama" feed.
|
||||
$feed = Feed::create(array(
|
||||
$feed = Feed::create([
|
||||
'title' => 'Llama',
|
||||
'url' => 'https://www.drupal.org/',
|
||||
'refresh' => 900,
|
||||
'checked' => 1389919932,
|
||||
'description' => 'Drupal.org',
|
||||
));
|
||||
]);
|
||||
$feed->save();
|
||||
|
||||
return $feed;
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
/**
|
||||
* Tests the fetcher plugins functionality and discoverability.
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
|
@ -30,7 +30,7 @@ class FeedParserTest extends AggregatorTestBase {
|
|||
$feed = $this->createFeed($this->getRSS091Sample());
|
||||
$feed->refreshItems();
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label())));
|
||||
$this->assertResponse(200, format_string('Feed %name exists.', ['%name' => $feed->label()]));
|
||||
$this->assertText('First example feed item title');
|
||||
$this->assertLinkByHref('http://example.com/example-turns-one');
|
||||
$this->assertText('First example feed item description.');
|
||||
|
@ -53,19 +53,19 @@ class FeedParserTest extends AggregatorTestBase {
|
|||
$feed = $this->createFeed($this->getAtomSample());
|
||||
$feed->refreshItems();
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label())));
|
||||
$this->assertResponse(200, format_string('Feed %name exists.', ['%name' => $feed->label()]));
|
||||
$this->assertText('Atom-Powered Robots Run Amok');
|
||||
$this->assertLinkByHref('http://example.org/2003/12/13/atom03');
|
||||
$this->assertText('Some text.');
|
||||
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(':link' => 'http://example.org/2003/12/13/atom03'))->fetchField(), 'Atom entry id element is parsed correctly.');
|
||||
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', [':link' => 'http://example.org/2003/12/13/atom03'])->fetchField(), 'Atom entry id element is parsed correctly.');
|
||||
|
||||
// Check for second feed entry.
|
||||
$this->assertText('We tried to stop them, but we failed.');
|
||||
$this->assertLinkByHref('http://example.org/2003/12/14/atom03');
|
||||
$this->assertText('Some other text.');
|
||||
$db_guid = db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(
|
||||
$db_guid = db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', [
|
||||
':link' => 'http://example.org/2003/12/14/atom03',
|
||||
))->fetchField();
|
||||
])->fetchField();
|
||||
$this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-bbbb-80da344efa6a', $db_guid, 'Atom entry id element is parsed correctly.');
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ class FeedParserTest extends AggregatorTestBase {
|
|||
$feed = $this->createFeed($this->getHtmlEntitiesSample());
|
||||
$feed->refreshItems();
|
||||
$this->drupalGet('aggregator/sources/' . $feed->id());
|
||||
$this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->label())));
|
||||
$this->assertResponse(200, format_string('Feed %name exists.', ['%name' => $feed->label()]));
|
||||
$this->assertRaw("Quote" Amp&");
|
||||
}
|
||||
|
||||
|
@ -85,12 +85,12 @@ class FeedParserTest extends AggregatorTestBase {
|
|||
*/
|
||||
public function testRedirectFeed() {
|
||||
$redirect_url = Url::fromRoute('aggregator_test.redirect')->setAbsolute()->toString();
|
||||
$feed = Feed::create(array('url' => $redirect_url, 'title' => $this->randomMachineName()));
|
||||
$feed = Feed::create(['url' => $redirect_url, 'title' => $this->randomMachineName()]);
|
||||
$feed->save();
|
||||
$feed->refreshItems();
|
||||
|
||||
// Make sure that the feed URL was updated correctly.
|
||||
$this->assertEqual($feed->getUrl(), \Drupal::url('aggregator_test.feed', array(), array('absolute' => TRUE)));
|
||||
$this->assertEqual($feed->getUrl(), \Drupal::url('aggregator_test.feed', [], ['absolute' => TRUE]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,13 +99,13 @@ class FeedParserTest extends AggregatorTestBase {
|
|||
public function testInvalidFeed() {
|
||||
// Simulate a typo in the URL to force a curl exception.
|
||||
$invalid_url = 'http:/www.drupal.org';
|
||||
$feed = Feed::create(array('url' => $invalid_url, 'title' => $this->randomMachineName()));
|
||||
$feed = Feed::create(['url' => $invalid_url, 'title' => $this->randomMachineName()]);
|
||||
$feed->save();
|
||||
|
||||
// Update the feed. Use the UI to be able to check the message easily.
|
||||
$this->drupalGet('admin/config/services/aggregator');
|
||||
$this->clickLink(t('Update items'));
|
||||
$this->assertRaw(t('The feed from %title seems to be broken because of error', array('%title' => $feed->label())));
|
||||
$this->assertRaw(t('The feed from %title seems to be broken because of error', ['%title' => $feed->label()]));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
|
@ -45,7 +45,7 @@ class FeedProcessorPluginTest extends AggregatorTestBase {
|
|||
$description = $feed->description->value ?: '';
|
||||
$this->updateAndDelete($feed, NULL);
|
||||
// Make sure the feed title is changed.
|
||||
$entities = entity_load_multiple_by_properties('aggregator_feed', array('description' => $description));
|
||||
$entities = entity_load_multiple_by_properties('aggregator_feed', ['description' => $description]);
|
||||
$this->assertTrue(empty($entities));
|
||||
}
|
||||
|
||||
|
@ -53,11 +53,11 @@ class FeedProcessorPluginTest extends AggregatorTestBase {
|
|||
* Test post-processing functionality.
|
||||
*/
|
||||
public function testPostProcess() {
|
||||
$feed = $this->createFeed(NULL, array('refresh' => 1800));
|
||||
$feed = $this->createFeed(NULL, ['refresh' => 1800]);
|
||||
$this->updateFeedItems($feed);
|
||||
$feed_id = $feed->id();
|
||||
// Reset entity cache manually.
|
||||
\Drupal::entityManager()->getStorage('aggregator_feed')->resetCache(array($feed_id));
|
||||
\Drupal::entityManager()->getStorage('aggregator_feed')->resetCache([$feed_id]);
|
||||
// Reload the feed to get new values.
|
||||
$feed = Feed::load($feed_id);
|
||||
// Make sure its refresh rate doubled.
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
/**
|
||||
* Tests OPML import.
|
||||
|
@ -14,7 +14,7 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('block', 'help');
|
||||
public static $modules = ['block', 'help'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -22,7 +22,7 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$admin_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create article content', 'administer blocks'));
|
||||
$admin_user = $this->drupalCreateUser(['administer news feeds', 'access news feeds', 'create article content', 'administer blocks']);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
*/
|
||||
public function openImportForm() {
|
||||
// Enable the help block.
|
||||
$this->drupalPlaceBlock('help_block', array('region' => 'help'));
|
||||
$this->drupalPlaceBlock('help_block', ['region' => 'help']);
|
||||
|
||||
$this->drupalGet('admin/config/services/aggregator/add/opml');
|
||||
$this->assertText('A single OPML document may contain many feeds.', 'Found OPML help text.');
|
||||
|
@ -46,19 +46,19 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
public function validateImportFormFields() {
|
||||
$before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertRaw(t('<em>Either</em> upload a file or enter a URL.'), 'Error if no fields are filled.');
|
||||
|
||||
$path = $this->getEmptyOpml();
|
||||
$edit = array(
|
||||
$edit = [
|
||||
'files[upload]' => $path,
|
||||
'remote' => file_create_url($path),
|
||||
);
|
||||
];
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertRaw(t('<em>Either</em> upload a file or enter a URL.'), 'Error if both fields are filled.');
|
||||
|
||||
$edit = array('remote' => 'invalidUrl://empty');
|
||||
$edit = ['remote' => 'invalidUrl://empty'];
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertText(t('The URL invalidUrl://empty is not valid.'), 'Error if the URL is invalid.');
|
||||
|
||||
|
@ -76,7 +76,7 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $form, t('Import'));
|
||||
$this->assertText(t('No new feed has been added.'), 'Attempting to upload invalid XML.');
|
||||
|
||||
$edit = array('remote' => file_create_url($this->getEmptyOpml()));
|
||||
$edit = ['remote' => file_create_url($this->getEmptyOpml())];
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertText(t('No new feed has been added.'), 'Attempting to load empty OPML from remote URL.');
|
||||
|
||||
|
@ -88,13 +88,13 @@ class ImportOpmlTest extends AggregatorTestBase {
|
|||
$feeds[0] = $this->getFeedEditArray();
|
||||
$feeds[1] = $this->getFeedEditArray();
|
||||
$feeds[2] = $this->getFeedEditArray();
|
||||
$edit = array(
|
||||
$edit = [
|
||||
'files[upload]' => $this->getValidOpml($feeds),
|
||||
'refresh' => '900',
|
||||
);
|
||||
];
|
||||
$this->drupalPostForm('admin/config/services/aggregator/add/opml', $edit, t('Import'));
|
||||
$this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['url[0][value]'])), 'Verifying that a duplicate URL was identified');
|
||||
$this->assertRaw(t('A feed named %title already exists.', array('%title' => $feeds[1]['title[0][value]'])), 'Verifying that a duplicate title was identified');
|
||||
$this->assertRaw(t('A feed with the URL %url already exists.', ['%url' => $feeds[0]['url[0][value]']]), 'Verifying that a duplicate URL was identified');
|
||||
$this->assertRaw(t('A feed named %title already exists.', ['%title' => $feeds[1]['title[0][value]']]), 'Verifying that a duplicate title was identified');
|
||||
|
||||
$after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
|
||||
$this->assertEqual($after, 2, 'Verifying that two distinct feeds were added.');
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\aggregator\Tests;
|
||||
namespace Drupal\Tests\aggregator\Functional;
|
||||
|
||||
use Drupal\aggregator\Entity\Feed;
|
||||
use Drupal\aggregator\Entity\Item;
|
||||
|
@ -19,7 +19,7 @@ class ItemCacheTagsTest extends EntityCacheTagsTestBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = array('aggregator');
|
||||
public static $modules = ['aggregator'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
@ -39,21 +39,21 @@ class ItemCacheTagsTest extends EntityCacheTagsTestBase {
|
|||
*/
|
||||
protected function createEntity() {
|
||||
// Create a "Camelids" feed.
|
||||
$feed = Feed::create(array(
|
||||
$feed = Feed::create([
|
||||
'title' => 'Camelids',
|
||||
'url' => 'https://groups.drupal.org/not_used/167169',
|
||||
'refresh' => 900,
|
||||
'checked' => 1389919932,
|
||||
'description' => 'Drupal Core Group feed',
|
||||
));
|
||||
]);
|
||||
$feed->save();
|
||||
|
||||
// Create a "Llama" aggregator feed item.
|
||||
$item = Item::create(array(
|
||||
$item = Item::create([
|
||||
'fid' => $feed->id(),
|
||||
'title' => t('Llama'),
|
||||
'path' => 'https://www.drupal.org/',
|
||||
));
|
||||
]);
|
||||
$item->save();
|
||||
|
||||
return $item;
|
||||
|
@ -67,14 +67,14 @@ class ItemCacheTagsTest extends EntityCacheTagsTestBase {
|
|||
\Drupal::cache('render')->set('foo', 'bar', CacheBackendInterface::CACHE_PERMANENT, $this->entity->getCacheTags());
|
||||
|
||||
// Verify a cache hit.
|
||||
$this->verifyRenderCache('foo', array('aggregator_feed:1'));
|
||||
$this->verifyRenderCache('foo', ['aggregator_feed:1']);
|
||||
|
||||
// Now create a feed item in that feed.
|
||||
Item::create(array(
|
||||
Item::create([
|
||||
'fid' => $this->entity->getFeedId(),
|
||||
'title' => t('Llama 2'),
|
||||
'path' => 'https://groups.drupal.org/',
|
||||
))->save();
|
||||
])->save();
|
||||
|
||||
// Verify a cache miss.
|
||||
$this->assertFalse(\Drupal::cache('render')->get('foo'), 'Creating a new feed item invalidates the cache tag of the feed.');
|
|
@ -17,7 +17,7 @@ class FeedValidationTest extends EntityKernelTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('aggregator', 'options');
|
||||
public static $modules = ['aggregator', 'options'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Drupal\Tests\aggregator\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\config\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
|
@ -34,7 +34,7 @@ class MigrateAggregatorConfigsTest extends MigrateDrupal6TestBase {
|
|||
$config = $this->config('aggregator.settings');
|
||||
$this->assertIdentical('aggregator', $config->get('fetcher'));
|
||||
$this->assertIdentical('aggregator', $config->get('parser'));
|
||||
$this->assertIdentical(array('aggregator'), $config->get('processors'));
|
||||
$this->assertIdentical(['aggregator'], $config->get('processors'));
|
||||
$this->assertIdentical(600, $config->get('items.teaser_length'));
|
||||
$this->assertIdentical('<a> <b> <br /> <dd> <dl> <dt> <em> <i> <li> <ol> <p> <strong> <u> <ul>', $config->get('items.allowed_html'));
|
||||
$this->assertIdentical(9676800, $config->get('items.expire'));
|
||||
|
|
|
@ -21,14 +21,14 @@ class IntegrationTest extends ViewsKernelTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('aggregator', 'aggregator_test_views', 'system', 'field', 'options', 'user');
|
||||
public static $modules = ['aggregator', 'aggregator_test_views', 'system', 'field', 'options', 'user'];
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = array('test_aggregator_items');
|
||||
public static $testViews = ['test_aggregator_items'];
|
||||
|
||||
/**
|
||||
* The entity storage for aggregator items.
|
||||
|
@ -53,7 +53,7 @@ class IntegrationTest extends ViewsKernelTestBase {
|
|||
$this->installEntitySchema('aggregator_item');
|
||||
$this->installEntitySchema('aggregator_feed');
|
||||
|
||||
ViewTestData::createTestViews(get_class($this), array('aggregator_test_views'));
|
||||
ViewTestData::createTestViews(get_class($this), ['aggregator_test_views']);
|
||||
|
||||
$this->itemStorage = $this->container->get('entity.manager')->getStorage('aggregator_item');
|
||||
$this->feedStorage = $this->container->get('entity.manager')->getStorage('aggregator_feed');
|
||||
|
@ -66,19 +66,19 @@ class IntegrationTest extends ViewsKernelTestBase {
|
|||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = \Drupal::service('renderer');
|
||||
|
||||
$feed = $this->feedStorage->create(array(
|
||||
$feed = $this->feedStorage->create([
|
||||
'title' => $this->randomMachineName(),
|
||||
'url' => 'https://www.drupal.org/',
|
||||
'refresh' => 900,
|
||||
'checked' => 123543535,
|
||||
'description' => $this->randomMachineName(),
|
||||
));
|
||||
]);
|
||||
$feed->save();
|
||||
|
||||
$items = array();
|
||||
$expected = array();
|
||||
$items = [];
|
||||
$expected = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$values = array();
|
||||
$values = [];
|
||||
$values['fid'] = $feed->id();
|
||||
$values['timestamp'] = mt_rand(REQUEST_TIME - 10, REQUEST_TIME + 10);
|
||||
$values['title'] = $this->randomMachineName();
|
||||
|
@ -99,13 +99,13 @@ class IntegrationTest extends ViewsKernelTestBase {
|
|||
$view = Views::getView('test_aggregator_items');
|
||||
$this->executeView($view);
|
||||
|
||||
$column_map = array(
|
||||
$column_map = [
|
||||
'iid' => 'iid',
|
||||
'title' => 'title',
|
||||
'aggregator_item_timestamp' => 'timestamp',
|
||||
'description' => 'description',
|
||||
'aggregator_item_author' => 'author',
|
||||
);
|
||||
];
|
||||
$this->assertIdenticalResultset($view, $expected, $column_map);
|
||||
|
||||
// Ensure that the rendering of the linked title works as expected.
|
||||
|
|
|
@ -15,7 +15,7 @@ class AggregatorLocalTasksTest extends LocalTaskIntegrationTestBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->directoryList = array('aggregator' => 'core/modules/aggregator');
|
||||
$this->directoryList = ['aggregator' => 'core/modules/aggregator'];
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
|
@ -25,19 +25,19 @@ class AggregatorLocalTasksTest extends LocalTaskIntegrationTestBase {
|
|||
* @dataProvider getAggregatorAdminRoutes
|
||||
*/
|
||||
public function testAggregatorAdminLocalTasks($route) {
|
||||
$this->assertLocalTasks($route, array(
|
||||
0 => array('aggregator.admin_overview', 'aggregator.admin_settings'),
|
||||
));
|
||||
$this->assertLocalTasks($route, [
|
||||
0 => ['aggregator.admin_overview', 'aggregator.admin_settings'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of routes to test.
|
||||
*/
|
||||
public function getAggregatorAdminRoutes() {
|
||||
return array(
|
||||
array('aggregator.admin_overview'),
|
||||
array('aggregator.admin_settings'),
|
||||
);
|
||||
return [
|
||||
['aggregator.admin_overview'],
|
||||
['aggregator.admin_settings'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,9 +46,9 @@ class AggregatorLocalTasksTest extends LocalTaskIntegrationTestBase {
|
|||
* @dataProvider getAggregatorSourceRoutes
|
||||
*/
|
||||
public function testAggregatorSourceLocalTasks($route) {
|
||||
$this->assertLocalTasks($route, array(
|
||||
0 => array('entity.aggregator_feed.canonical', 'entity.aggregator_feed.edit_form', 'entity.aggregator_feed.delete_form'),
|
||||
));
|
||||
$this->assertLocalTasks($route, [
|
||||
0 => ['entity.aggregator_feed.canonical', 'entity.aggregator_feed.edit_form', 'entity.aggregator_feed.delete_form'],
|
||||
]);
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -56,10 +56,10 @@ class AggregatorLocalTasksTest extends LocalTaskIntegrationTestBase {
|
|||
* Provides a list of source routes to test.
|
||||
*/
|
||||
public function getAggregatorSourceRoutes() {
|
||||
return array(
|
||||
array('entity.aggregator_feed.canonical'),
|
||||
array('entity.aggregator_feed.edit_form'),
|
||||
);
|
||||
return [
|
||||
['entity.aggregator_feed.canonical'],
|
||||
['entity.aggregator_feed.edit_form'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,20 +39,20 @@ class AggregatorPluginSettingsBaseTest extends UnitTestCase {
|
|||
*/
|
||||
protected function setUp() {
|
||||
$this->configFactory = $this->getConfigFactoryStub(
|
||||
array(
|
||||
'aggregator.settings' => array(
|
||||
'processors' => array('aggregator_test'),
|
||||
),
|
||||
'aggregator_test.settings' => array(),
|
||||
)
|
||||
[
|
||||
'aggregator.settings' => [
|
||||
'processors' => ['aggregator_test'],
|
||||
],
|
||||
'aggregator_test.settings' => [],
|
||||
]
|
||||
);
|
||||
foreach (array('fetcher', 'parser', 'processor') as $type) {
|
||||
foreach (['fetcher', 'parser', 'processor'] as $type) {
|
||||
$this->managers[$type] = $this->getMockBuilder('Drupal\aggregator\Plugin\AggregatorPluginManager')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->managers[$type]->expects($this->once())
|
||||
->method('getDefinitions')
|
||||
->will($this->returnValue(array('aggregator_test' => array('title' => '', 'description' => ''))));
|
||||
->will($this->returnValue(['aggregator_test' => ['title' => '', 'description' => '']]));
|
||||
}
|
||||
|
||||
$this->settingsForm = new SettingsForm(
|
||||
|
@ -79,8 +79,8 @@ class AggregatorPluginSettingsBaseTest extends UnitTestCase {
|
|||
|
||||
$test_processor = $this->getMock(
|
||||
'Drupal\aggregator_test\Plugin\aggregator\processor\TestProcessor',
|
||||
array('buildConfigurationForm', 'validateConfigurationForm', 'submitConfigurationForm'),
|
||||
array(array(), 'aggregator_test', array('description' => ''), $this->configFactory)
|
||||
['buildConfigurationForm', 'validateConfigurationForm', 'submitConfigurationForm'],
|
||||
[[], 'aggregator_test', ['description' => ''], $this->configFactory]
|
||||
);
|
||||
$test_processor->expects($this->at(0))
|
||||
->method('buildConfigurationForm')
|
||||
|
@ -98,7 +98,7 @@ class AggregatorPluginSettingsBaseTest extends UnitTestCase {
|
|||
->with($this->equalTo('aggregator_test'))
|
||||
->will($this->returnValue($test_processor));
|
||||
|
||||
$form = $this->settingsForm->buildForm(array(), $form_state);
|
||||
$form = $this->settingsForm->buildForm([], $form_state);
|
||||
$this->settingsForm->validateForm($form, $form_state);
|
||||
$this->settingsForm->submitForm($form, $form_state);
|
||||
}
|
||||
|
|
|
@ -34,14 +34,8 @@ function automated_cron_help($route_name, RouteMatchInterface $route_match) {
|
|||
function automated_cron_form_system_cron_settings_alter(&$form, &$form_state) {
|
||||
$automated_cron_settings = \Drupal::config('automated_cron.settings');
|
||||
|
||||
// Add automated cron settings.
|
||||
$form['automated_cron'] = [
|
||||
'#title' => t('Cron settings'),
|
||||
'#type' => 'details',
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$options = [3600, 10800, 21600, 43200, 86400, 604800];
|
||||
$form['automated_cron']['interval'] = [
|
||||
$form['cron']['interval'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('Run cron every'),
|
||||
'#description' => t('More information about setting up scheduled tasks can be found by <a href="@url">reading the cron tutorial on drupal.org</a>.', ['@url' => 'https://www.drupal.org/cron']),
|
||||
|
@ -49,13 +43,6 @@ function automated_cron_form_system_cron_settings_alter(&$form, &$form_state) {
|
|||
'#options' => [0 => t('Never')] + array_map([\Drupal::service('date.formatter'), 'formatInterval'], array_combine($options, $options)),
|
||||
];
|
||||
|
||||
$form['actions']['#type'] = 'actions';
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save configuration'),
|
||||
'#button_type' => 'primary',
|
||||
];
|
||||
|
||||
// Add submit callback.
|
||||
$form['#submit'][] = 'automated_cron_settings_submit';
|
||||
|
||||
|
@ -70,5 +57,4 @@ function automated_cron_settings_submit(array $form, FormStateInterface $form_st
|
|||
\Drupal::configFactory()->getEditable('automated_cron.settings')
|
||||
->set('interval', $form_state->getValue('interval'))
|
||||
->save();
|
||||
drupal_set_message(t('The configuration options have been saved.'));
|
||||
}
|
||||
|
|
|
@ -9,27 +9,27 @@
|
|||
* Implements hook_schema().
|
||||
*/
|
||||
function ban_schema() {
|
||||
$schema['ban_ip'] = array(
|
||||
$schema['ban_ip'] = [
|
||||
'description' => 'Stores banned IP addresses.',
|
||||
'fields' => array(
|
||||
'iid' => array(
|
||||
'fields' => [
|
||||
'iid' => [
|
||||
'description' => 'Primary Key: unique ID for IP addresses.',
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'ip' => array(
|
||||
],
|
||||
'ip' => [
|
||||
'description' => 'IP address',
|
||||
'type' => 'varchar_ascii',
|
||||
'length' => 40,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'ip' => array('ip'),
|
||||
),
|
||||
'primary key' => array('iid'),
|
||||
);
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
'ip' => ['ip'],
|
||||
],
|
||||
'primary key' => ['iid'],
|
||||
];
|
||||
return $schema;
|
||||
}
|
||||
|
|
|
@ -15,11 +15,11 @@ function ban_help($route_name, RouteMatchInterface $route_match) {
|
|||
case 'help.page.ban':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The Ban module allows administrators to ban visits to their site from individual IP addresses. For more information, see the <a href=":url">online documentation for the Ban module</a>.', array(':url' => 'https://www.drupal.org/documentation/modules/ban')) . '</p>';
|
||||
$output .= '<p>' . t('The Ban module allows administrators to ban visits to their site from individual IP addresses. For more information, see the <a href=":url">online documentation for the Ban module</a>.', [':url' => 'https://www.drupal.org/documentation/modules/ban']) . '</p>';
|
||||
$output .= '<h3>' . t('Uses') . '</h3>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . t('Banning IP addresses') . '</dt>';
|
||||
$output .= '<dd>' . t('Administrators can enter IP addresses to ban on the <a href=":bans">IP address bans</a> page.', array(':bans' => \Drupal::url('ban.admin_page'))) . '</dd>';
|
||||
$output .= '<dd>' . t('Administrators can enter IP addresses to ban on the <a href=":bans">IP address bans</a> page.', [':bans' => \Drupal::url('ban.admin_page')]) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ class BanIpManager implements BanIpManagerInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function isBanned($ip) {
|
||||
return (bool) $this->connection->query("SELECT * FROM {ban_ip} WHERE ip = :ip", array(':ip' => $ip))->fetchField();
|
||||
return (bool) $this->connection->query("SELECT * FROM {ban_ip} WHERE ip = :ip", [':ip' => $ip])->fetchField();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,8 +45,8 @@ class BanIpManager implements BanIpManagerInterface {
|
|||
*/
|
||||
public function banIp($ip) {
|
||||
$this->connection->merge('ban_ip')
|
||||
->key(array('ip' => $ip))
|
||||
->fields(array('ip' => $ip))
|
||||
->key(['ip' => $ip])
|
||||
->fields(['ip' => $ip])
|
||||
->execute();
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ class BanIpManager implements BanIpManagerInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function findById($ban_id) {
|
||||
return $this->connection->query("SELECT ip FROM {ban_ip} WHERE iid = :iid", array(':iid' => $ban_id))->fetchField();
|
||||
return $this->connection->query("SELECT ip FROM {ban_ip} WHERE iid = :iid", [':iid' => $ban_id])->fetchField();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -52,47 +52,47 @@ class BanAdmin extends FormBase {
|
|||
* address form field.
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state, $default_ip = '') {
|
||||
$rows = array();
|
||||
$header = array($this->t('banned IP addresses'), $this->t('Operations'));
|
||||
$rows = [];
|
||||
$header = [$this->t('banned IP addresses'), $this->t('Operations')];
|
||||
$result = $this->ipManager->findAll();
|
||||
foreach ($result as $ip) {
|
||||
$row = array();
|
||||
$row = [];
|
||||
$row[] = $ip->ip;
|
||||
$links = array();
|
||||
$links['delete'] = array(
|
||||
$links = [];
|
||||
$links['delete'] = [
|
||||
'title' => $this->t('Delete'),
|
||||
'url' => Url::fromRoute('ban.delete', ['ban_id' => $ip->iid]),
|
||||
);
|
||||
$row[] = array(
|
||||
'data' => array(
|
||||
];
|
||||
$row[] = [
|
||||
'data' => [
|
||||
'#type' => 'operations',
|
||||
'#links' => $links,
|
||||
),
|
||||
);
|
||||
],
|
||||
];
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$form['ip'] = array(
|
||||
$form['ip'] = [
|
||||
'#title' => $this->t('IP address'),
|
||||
'#type' => 'textfield',
|
||||
'#size' => 48,
|
||||
'#maxlength' => 40,
|
||||
'#default_value' => $default_ip,
|
||||
'#description' => $this->t('Enter a valid IP address.'),
|
||||
);
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
$form['actions']['submit'] = array(
|
||||
];
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Add'),
|
||||
);
|
||||
];
|
||||
|
||||
$form['ban_ip_banning_table'] = array(
|
||||
$form['ban_ip_banning_table'] = [
|
||||
'#type' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
'#empty' => $this->t('No blocked IP addresses available.'),
|
||||
'#weight' => 120,
|
||||
);
|
||||
];
|
||||
return $form;
|
||||
}
|
||||
|
||||
|
@ -118,7 +118,7 @@ class BanAdmin extends FormBase {
|
|||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$ip = trim($form_state->getValue('ip'));
|
||||
$this->ipManager->banIp($ip);
|
||||
drupal_set_message($this->t('The IP address %ip has been banned.', array('%ip' => $ip)));
|
||||
drupal_set_message($this->t('The IP address %ip has been banned.', ['%ip' => $ip]));
|
||||
$form_state->setRedirect('ban.admin_page');
|
||||
}
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ class BanDelete extends ConfirmFormBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function getQuestion() {
|
||||
return $this->t('Are you sure you want to unblock %ip?', array('%ip' => $this->banIp));
|
||||
return $this->t('Are you sure you want to unblock %ip?', ['%ip' => $this->banIp]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -93,8 +93,8 @@ class BanDelete extends ConfirmFormBase {
|
|||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->ipManager->unbanIp($this->banIp);
|
||||
$this->logger('user')->notice('Deleted %ip', array('%ip' => $this->banIp));
|
||||
drupal_set_message($this->t('The IP address %ip was deleted.', array('%ip' => $this->banIp)));
|
||||
$this->logger('user')->notice('Deleted %ip', ['%ip' => $this->banIp]);
|
||||
drupal_set_message($this->t('The IP address %ip was deleted.', ['%ip' => $this->banIp]));
|
||||
$form_state->setRedirectUrl($this->getCancelUrl());
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ class BlockedIP extends DestinationBase implements ContainerFactoryPluginInterfa
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
$this->banManager->banIp($row->getDestinationProperty('ip'));
|
||||
}
|
||||
|
||||
|
|
|
@ -18,55 +18,55 @@ class IpAddressBlockingTest extends BrowserTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('ban');
|
||||
public static $modules = ['ban'];
|
||||
|
||||
/**
|
||||
* Tests various user input to confirm correct validation and saving of data.
|
||||
*/
|
||||
function testIPAddressValidation() {
|
||||
public function testIPAddressValidation() {
|
||||
// Create user.
|
||||
$admin_user = $this->drupalCreateUser(array('ban IP addresses'));
|
||||
$admin_user = $this->drupalCreateUser(['ban IP addresses']);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/config/people/ban');
|
||||
|
||||
// Ban a valid IP address.
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$edit['ip'] = '1.2.3.3';
|
||||
$this->drupalPostForm('admin/config/people/ban', $edit, t('Add'));
|
||||
$ip = db_query("SELECT iid from {ban_ip} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField();
|
||||
$ip = db_query("SELECT iid from {ban_ip} WHERE ip = :ip", [':ip' => $edit['ip']])->fetchField();
|
||||
$this->assertTrue($ip, 'IP address found in database.');
|
||||
$this->assertRaw(t('The IP address %ip has been banned.', array('%ip' => $edit['ip'])), 'IP address was banned.');
|
||||
$this->assertRaw(t('The IP address %ip has been banned.', ['%ip' => $edit['ip']]), 'IP address was banned.');
|
||||
|
||||
// Try to block an IP address that's already blocked.
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$edit['ip'] = '1.2.3.3';
|
||||
$this->drupalPostForm('admin/config/people/ban', $edit, t('Add'));
|
||||
$this->assertText(t('This IP address is already banned.'));
|
||||
|
||||
// Try to block a reserved IP address.
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$edit['ip'] = '255.255.255.255';
|
||||
$this->drupalPostForm('admin/config/people/ban', $edit, t('Add'));
|
||||
$this->assertText(t('Enter a valid IP address.'));
|
||||
|
||||
// Try to block a reserved IP address.
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$edit['ip'] = 'test.example.com';
|
||||
$this->drupalPostForm('admin/config/people/ban', $edit, t('Add'));
|
||||
$this->assertText(t('Enter a valid IP address.'));
|
||||
|
||||
// Submit an empty form.
|
||||
$edit = array();
|
||||
$edit = [];
|
||||
$edit['ip'] = '';
|
||||
$this->drupalPostForm('admin/config/people/ban', $edit, t('Add'));
|
||||
$this->assertText(t('Enter a valid IP address.'));
|
||||
|
||||
// Pass an IP address as a URL parameter and submit it.
|
||||
$submit_ip = '1.2.3.4';
|
||||
$this->drupalPostForm('admin/config/people/ban/' . $submit_ip, array(), t('Add'));
|
||||
$ip = db_query("SELECT iid from {ban_ip} WHERE ip = :ip", array(':ip' => $submit_ip))->fetchField();
|
||||
$this->drupalPostForm('admin/config/people/ban/' . $submit_ip, [], t('Add'));
|
||||
$ip = db_query("SELECT iid from {ban_ip} WHERE ip = :ip", [':ip' => $submit_ip])->fetchField();
|
||||
$this->assertTrue($ip, 'IP address found in database');
|
||||
$this->assertRaw(t('The IP address %ip has been banned.', array('%ip' => $submit_ip)), 'IP address was banned.');
|
||||
$this->assertRaw(t('The IP address %ip has been banned.', ['%ip' => $submit_ip]), 'IP address was banned.');
|
||||
|
||||
// Submit your own IP address. This fails, although it works when testing
|
||||
// manually.
|
||||
|
@ -85,7 +85,7 @@ class IpAddressBlockingTest extends BrowserTestBase {
|
|||
$banIp->banIp($ip);
|
||||
$banIp->banIp($ip);
|
||||
$query = db_select('ban_ip', 'bip');
|
||||
$query->fields('bip', array('iid'));
|
||||
$query->fields('bip', ['iid']);
|
||||
$query->condition('bip.ip', $ip);
|
||||
$ip_count = $query->execute()->fetchAll();
|
||||
$this->assertEqual(1, count($ip_count));
|
||||
|
@ -93,7 +93,7 @@ class IpAddressBlockingTest extends BrowserTestBase {
|
|||
$banIp->banIp($ip);
|
||||
$banIp->banIp($ip);
|
||||
$query = db_select('ban_ip', 'bip');
|
||||
$query->fields('bip', array('iid'));
|
||||
$query->fields('bip', ['iid']);
|
||||
$query->condition('bip.ip', $ip);
|
||||
$ip_count = $query->execute()->fetchAll();
|
||||
$this->assertEqual(1, count($ip_count));
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace Drupal\Tests\ban\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\config\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,7 +15,7 @@ function basic_auth_help($route_name, RouteMatchInterface $route_match) {
|
|||
case 'help.page.basic_auth':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . t('The HTTP Basic Authentication module supplies an <a href="http://en.wikipedia.org/wiki/Basic_access_authentication">HTTP Basic authentication</a> provider for web service requests. This authentication provider authenticates requests using the HTTP Basic Authentication username and password, as an alternative to using Drupal\'s standard cookie-based authentication system. It is only useful if your site provides web services configured to use this type of authentication (for instance, the <a href=":rest_help">RESTful Web Services module</a>). For more information, see the <a href=":hba_do">online documentation for the HTTP Basic Authentication module</a>.', array(':hba_do' => 'https://www.drupal.org/documentation/modules/basic_auth', ':rest_help' => (\Drupal::moduleHandler()->moduleExists('rest')) ? \Drupal::url('help.page', array('name' => 'rest')) : '#')) . '</p>';
|
||||
$output .= '<p>' . t('The HTTP Basic Authentication module supplies an <a href="http://en.wikipedia.org/wiki/Basic_access_authentication">HTTP Basic authentication</a> provider for web service requests. This authentication provider authenticates requests using the HTTP Basic Authentication username and password, as an alternative to using Drupal\'s standard cookie-based authentication system. It is only useful if your site provides web services configured to use this type of authentication (for instance, the <a href=":rest_help">RESTful Web Services module</a>). For more information, see the <a href=":hba_do">online documentation for the HTTP Basic Authentication module</a>.', [':hba_do' => 'https://www.drupal.org/documentation/modules/basic_auth', ':rest_help' => (\Drupal::moduleHandler()->moduleExists('rest')) ? \Drupal::url('help.page', ['name' => 'rest']) : '#']) . '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ class BasicAuth implements AuthenticationProviderInterface, AuthenticationProvid
|
|||
// in to many different user accounts. We have a reasonably high limit
|
||||
// since there may be only one apparent IP for all users at an institution.
|
||||
if ($this->flood->isAllowed('basic_auth.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
|
||||
$accounts = $this->entityManager->getStorage('user')->loadByProperties(array('name' => $username, 'status' => 1));
|
||||
$accounts = $this->entityManager->getStorage('user')->loadByProperties(['name' => $username, 'status' => 1]);
|
||||
$account = reset($accounts);
|
||||
if ($account) {
|
||||
if ($flood_config->get('uid_only')) {
|
||||
|
@ -127,9 +127,9 @@ class BasicAuth implements AuthenticationProviderInterface, AuthenticationProvid
|
|||
*/
|
||||
public function challengeException(Request $request, \Exception $previous) {
|
||||
$site_name = $this->configFactory->get('system.site')->get('name');
|
||||
$challenge = SafeMarkup::format('Basic realm="@realm"', array(
|
||||
$challenge = SafeMarkup::format('Basic realm="@realm"', [
|
||||
'@realm' => !empty($site_name) ? $site_name : 'Access restricted',
|
||||
));
|
||||
]);
|
||||
return new UnauthorizedHttpException((string) $challenge, 'No authentication credentials provided.', $previous);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,15 @@
|
|||
|
||||
namespace Drupal\basic_auth\Tests;
|
||||
|
||||
@trigger_error(__FILE__ . ' is deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0. Use \Drupal\Tests\basic_auth\Traits\BasicAuthTestTrait instead. See https://www.drupal.org/node/2862800.', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Provides common functionality for Basic Authentication test classes.
|
||||
*
|
||||
* @deprecated in Drupal 8.3.0 and will be removed before Drupal 9.0.0.
|
||||
* Use \Drupal\Tests\basic_auth\Traits\BasicAuthTestTrait instead.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2862800
|
||||
*/
|
||||
trait BasicAuthTestTrait {
|
||||
|
||||
|
@ -51,7 +58,7 @@ trait BasicAuthTestTrait {
|
|||
*
|
||||
* @see \Drupal\simpletest\WebTestBase::drupalPostForm()
|
||||
*/
|
||||
protected function basicAuthPostForm($path, $edit, $submit, $username, $password, array $options = array(), $form_html_id = NULL, $extra_post = NULL) {
|
||||
protected function basicAuthPostForm($path, $edit, $submit, $username, $password, array $options = [], $form_html_id = NULL, $extra_post = NULL) {
|
||||
return $this->drupalPostForm($path, $edit, $submit, $options, $this->getBasicAuthHeaders($username, $password), $form_html_id, $extra_post);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\basic_auth\Tests\Authentication;
|
||||
namespace Drupal\Tests\basic_auth\Functional;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\basic_auth\Tests\BasicAuthTestTrait;
|
||||
use Drupal\Tests\basic_auth\Traits\BasicAuthTestTrait;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests for BasicAuth authentication provider.
|
||||
*
|
||||
* @group basic_auth
|
||||
*/
|
||||
class BasicAuthTest extends WebTestBase {
|
||||
class BasicAuthTest extends BrowserTestBase {
|
||||
|
||||
use BasicAuthTestTrait;
|
||||
|
||||
|
@ -22,7 +22,7 @@ class BasicAuthTest extends WebTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('basic_auth', 'router_test', 'locale', 'basic_auth_test');
|
||||
public static $modules = ['basic_auth', 'router_test', 'locale', 'basic_auth_test'];
|
||||
|
||||
/**
|
||||
* Test http basic authentication.
|
||||
|
@ -39,14 +39,14 @@ class BasicAuthTest extends WebTestBase {
|
|||
$this->basicAuthGet($url, $account->getUsername(), $account->pass_raw);
|
||||
$this->assertText($account->getUsername(), 'Account name is displayed.');
|
||||
$this->assertResponse('200', 'HTTP response is OK');
|
||||
$this->curlClose();
|
||||
$this->mink->resetSessions();
|
||||
$this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'));
|
||||
$this->assertIdentical(strpos($this->drupalGetHeader('Cache-Control'), 'public'), FALSE, 'Cache-Control is not set to public');
|
||||
|
||||
$this->basicAuthGet($url, $account->getUsername(), $this->randomMachineName());
|
||||
$this->assertNoText($account->getUsername(), 'Bad basic auth credentials do not authenticate the user.');
|
||||
$this->assertResponse('403', 'Access is not granted.');
|
||||
$this->curlClose();
|
||||
$this->mink->resetSessions();
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertEqual($this->drupalGetHeader('WWW-Authenticate'), SafeMarkup::format('Basic realm="@realm"', ['@realm' => \Drupal::config('system.site')->get('name')]));
|
||||
|
@ -55,12 +55,12 @@ class BasicAuthTest extends WebTestBase {
|
|||
$this->drupalGet('admin');
|
||||
$this->assertResponse('403', 'No authentication prompt for routes not explicitly defining authentication providers.');
|
||||
|
||||
$account = $this->drupalCreateUser(array('access administration pages'));
|
||||
$account = $this->drupalCreateUser(['access administration pages']);
|
||||
|
||||
$this->basicAuthGet(Url::fromRoute('system.admin'), $account->getUsername(), $account->pass_raw);
|
||||
$this->assertNoLink('Log out', 'User is not logged in');
|
||||
$this->assertResponse('403', 'No basic authentication for routes not explicitly defining authentication providers.');
|
||||
$this->curlClose();
|
||||
$this->mink->resetSessions();
|
||||
|
||||
// Ensure that pages already in the page cache aren't returned from page
|
||||
// cache if basic auth credentials are provided.
|
||||
|
@ -75,14 +75,14 @@ class BasicAuthTest extends WebTestBase {
|
|||
/**
|
||||
* Test the global login flood control.
|
||||
*/
|
||||
function testGlobalLoginFloodControl() {
|
||||
public function testGlobalLoginFloodControl() {
|
||||
$this->config('user.flood')
|
||||
->set('ip_limit', 2)
|
||||
// Set a high per-user limit out so that it is not relevant in the test.
|
||||
->set('user_limit', 4000)
|
||||
->save();
|
||||
|
||||
$user = $this->drupalCreateUser(array());
|
||||
$user = $this->drupalCreateUser([]);
|
||||
$incorrect_user = clone $user;
|
||||
$incorrect_user->pass_raw .= 'incorrect';
|
||||
$url = Url::fromRoute('router_test.11');
|
||||
|
@ -100,17 +100,17 @@ class BasicAuthTest extends WebTestBase {
|
|||
/**
|
||||
* Test the per-user login flood control.
|
||||
*/
|
||||
function testPerUserLoginFloodControl() {
|
||||
public function testPerUserLoginFloodControl() {
|
||||
$this->config('user.flood')
|
||||
// Set a high global limit out so that it is not relevant in the test.
|
||||
->set('ip_limit', 4000)
|
||||
->set('user_limit', 2)
|
||||
->save();
|
||||
|
||||
$user = $this->drupalCreateUser(array());
|
||||
$user = $this->drupalCreateUser([]);
|
||||
$incorrect_user = clone $user;
|
||||
$incorrect_user->pass_raw .= 'incorrect';
|
||||
$user2 = $this->drupalCreateUser(array());
|
||||
$user2 = $this->drupalCreateUser([]);
|
||||
$url = Url::fromRoute('router_test.11');
|
||||
|
||||
// Try a failed login.
|
||||
|
@ -138,7 +138,7 @@ class BasicAuthTest extends WebTestBase {
|
|||
/**
|
||||
* Tests compatibility with locale/UI translation.
|
||||
*/
|
||||
function testLocale() {
|
||||
public function testLocale() {
|
||||
ConfigurableLanguage::createFromLangcode('de')->save();
|
||||
$this->config('system.site')->set('default_langcode', 'de')->save();
|
||||
|
||||
|
@ -148,13 +148,12 @@ class BasicAuthTest extends WebTestBase {
|
|||
$this->basicAuthGet($url, $account->getUsername(), $account->pass_raw);
|
||||
$this->assertText($account->getUsername(), 'Account name is displayed.');
|
||||
$this->assertResponse('200', 'HTTP response is OK');
|
||||
$this->curlClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a comprehensive message is displayed when the route is denied.
|
||||
*/
|
||||
function testUnauthorizedErrorMessage() {
|
||||
public function testUnauthorizedErrorMessage() {
|
||||
$account = $this->drupalCreateUser();
|
||||
$url = Url::fromRoute('router_test.11');
|
||||
|
||||
|
@ -173,6 +172,12 @@ class BasicAuthTest extends WebTestBase {
|
|||
$this->basicAuthGet($url, $account->getUsername(), $this->randomMachineName());
|
||||
$this->assertResponse('403', 'The user is blocked when wrong credentials are passed.');
|
||||
$this->assertText('Access denied', "A user friendly access denied message is displayed");
|
||||
|
||||
// Case when correct credentials but hasn't access to the route.
|
||||
$url = Url::fromRoute('router_test.15');
|
||||
$this->basicAuthGet($url, $account->getUsername(), $account->pass_raw);
|
||||
$this->assertResponse('403', 'The used authentication method is not allowed on this route.');
|
||||
$this->assertText('Access denied', "A user friendly access denied message is displayed");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,6 +196,8 @@ class BasicAuthTest extends WebTestBase {
|
|||
$this->basicAuthGet('/basic_auth_test/state/modify', $account->getUsername(), $account->pass_raw);
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('Done');
|
||||
|
||||
$this->mink->resetSessions();
|
||||
$this->drupalGet('/basic_auth_test/state/read');
|
||||
$this->assertResponse(200);
|
||||
$this->assertRaw('yep');
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\basic_auth\Traits;
|
||||
|
||||
/**
|
||||
* Provides common functionality for Basic Authentication test classes.
|
||||
*/
|
||||
trait BasicAuthTestTrait {
|
||||
|
||||
/**
|
||||
* Retrieves a Drupal path or an absolute path using basic authentication.
|
||||
*
|
||||
* @param \Drupal\Core\Url|string $path
|
||||
* Drupal path or URL to load into the internal browser.
|
||||
* @param string $username
|
||||
* The username to use for basic authentication.
|
||||
* @param string $password
|
||||
* The password to use for basic authentication.
|
||||
* @param array $options
|
||||
* (optional) Options to be forwarded to the url generator.
|
||||
*
|
||||
* @return string
|
||||
* The retrieved HTML string, also available as $this->getRawContent().
|
||||
*/
|
||||
protected function basicAuthGet($path, $username, $password, array $options = []) {
|
||||
return $this->drupalGet($path, $options, $this->getBasicAuthHeaders($username, $password));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a form submission using basic authentication.
|
||||
*
|
||||
* @param string $path
|
||||
* Location of the post form.
|
||||
* @param array $edit
|
||||
* Field data in an associative array.
|
||||
* @param string $submit
|
||||
* Value of the submit button whose click is to be emulated.
|
||||
* @param string $username
|
||||
* The username to use for basic authentication.
|
||||
* @param string $password
|
||||
* The password to use for basic authentication.
|
||||
* @param array $options
|
||||
* Options to be forwarded to the url generator.
|
||||
* @param string $form_html_id
|
||||
* (optional) HTML ID of the form to be submitted.
|
||||
* @param string $extra_post
|
||||
* (optional) A string of additional data to append to the POST submission.
|
||||
*
|
||||
* @return string
|
||||
* The retrieved HTML string.
|
||||
*
|
||||
* @see \Drupal\simpletest\WebTestBase::drupalPostForm()
|
||||
*/
|
||||
protected function basicAuthPostForm($path, $edit, $submit, $username, $password, array $options = [], $form_html_id = NULL, $extra_post = NULL) {
|
||||
return $this->drupalPostForm($path, $edit, $submit, $options, $this->getBasicAuthHeaders($username, $password), $form_html_id, $extra_post);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTTP headers that can be used for basic authentication in Curl.
|
||||
*
|
||||
* @param string $username
|
||||
* The username to use for basic authentication.
|
||||
* @param string $password
|
||||
* The password to use for basic authentication.
|
||||
*
|
||||
* @return array
|
||||
* An array of raw request headers as used by curl_setopt().
|
||||
*/
|
||||
protected function getBasicAuthHeaders($username, $password) {
|
||||
// Set up Curl to use basic authentication with the test user's credentials.
|
||||
return ['Authorization' => 'Basic ' . base64_encode("$username:$password")];
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
name: BigPipe
|
||||
type: module
|
||||
description: 'Sends pages using the BigPipe technique that allows browsers to show them much faster.'
|
||||
package: Core (Experimental)
|
||||
package: Core
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
|
|
|
@ -58,8 +58,7 @@ function big_pipe_page_attachments(array &$page) {
|
|||
'#noscript' => TRUE,
|
||||
'#attributes' => [
|
||||
'http-equiv' => 'Refresh',
|
||||
// @todo: Switch to Url::fromRoute() once https://www.drupal.org/node/2589967 is resolved.
|
||||
'content' => '0; URL=' . Url::fromUri('internal:/big_pipe/no-js', ['query' => \Drupal::service('redirect.destination')->getAsArray()])->toString(),
|
||||
'content' => '0; URL=' . Url::fromRoute('big_pipe.nojs', [], ['query' => \Drupal::service('redirect.destination')->getAsArray()])->toString(),
|
||||
],
|
||||
],
|
||||
'big_pipe_detect_nojs',
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
namespace Drupal\big_pipe\EventSubscriber;
|
||||
|
||||
use Drupal\Core\Render\HtmlResponse;
|
||||
use Drupal\big_pipe\Render\BigPipeInterface;
|
||||
use Drupal\big_pipe\Render\BigPipe;
|
||||
use Drupal\big_pipe\Render\BigPipeResponse;
|
||||
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
@ -12,7 +12,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
|||
/**
|
||||
* Response subscriber to replace the HtmlResponse with a BigPipeResponse.
|
||||
*
|
||||
* @see \Drupal\big_pipe\Render\BigPipeInterface
|
||||
* @see \Drupal\big_pipe\Render\BigPipe
|
||||
*
|
||||
* @todo Refactor once https://www.drupal.org/node/2577631 lands.
|
||||
*/
|
||||
|
@ -21,17 +21,17 @@ class HtmlResponseBigPipeSubscriber implements EventSubscriberInterface {
|
|||
/**
|
||||
* The BigPipe service.
|
||||
*
|
||||
* @var \Drupal\big_pipe\Render\BigPipeInterface
|
||||
* @var \Drupal\big_pipe\Render\BigPipe
|
||||
*/
|
||||
protected $bigPipe;
|
||||
|
||||
/**
|
||||
* Constructs a HtmlResponseBigPipeSubscriber object.
|
||||
*
|
||||
* @param \Drupal\big_pipe\Render\BigPipeInterface $big_pipe
|
||||
* @param \Drupal\big_pipe\Render\BigPipe $big_pipe
|
||||
* The BigPipe service.
|
||||
*/
|
||||
public function __construct(BigPipeInterface $big_pipe) {
|
||||
public function __construct(BigPipe $big_pipe) {
|
||||
$this->bigPipe = $big_pipe;
|
||||
}
|
||||
|
||||
|
@ -91,38 +91,24 @@ class HtmlResponseBigPipeSubscriber implements EventSubscriberInterface {
|
|||
return;
|
||||
}
|
||||
|
||||
$big_pipe_response = new BigPipeResponse();
|
||||
$big_pipe_response->setBigPipeService($this->bigPipe);
|
||||
|
||||
// Clone the HtmlResponse's data into the new BigPipeResponse.
|
||||
$big_pipe_response->headers = clone $response->headers;
|
||||
$big_pipe_response
|
||||
->setStatusCode($response->getStatusCode())
|
||||
->setContent($response->getContent())
|
||||
->setAttachments($attachments)
|
||||
->addCacheableDependency($response->getCacheableMetadata());
|
||||
|
||||
// A BigPipe response can never be cached, because it is intended for a
|
||||
// single user.
|
||||
// @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1
|
||||
$big_pipe_response->setPrivate();
|
||||
|
||||
// Inform surrogates how they should handle BigPipe responses:
|
||||
// - "no-store" specifies that the response should not be stored in cache;
|
||||
// it is only to be used for the original request
|
||||
// - "content" identifies what processing surrogates should perform on the
|
||||
// response before forwarding it. We send, "BigPipe/1.0", which surrogates
|
||||
// should not process at all, and in fact, they should not even buffer it
|
||||
// at all.
|
||||
// @see http://www.w3.org/TR/edge-arch/
|
||||
$big_pipe_response->headers->set('Surrogate-Control', 'no-store, content="BigPipe/1.0"');
|
||||
|
||||
// Add header to support streaming on NGINX + php-fpm (nginx >= 1.5.6).
|
||||
$big_pipe_response->headers->set('X-Accel-Buffering', 'no');
|
||||
|
||||
$big_pipe_response = new BigPipeResponse($response);
|
||||
$big_pipe_response->setBigPipeService($this->getBigPipeService($event));
|
||||
$event->setResponse($big_pipe_response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the BigPipe service to use to send the current response.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
|
||||
* A response event.
|
||||
*
|
||||
* @return \Drupal\big_pipe\Render\BigPipe
|
||||
* The BigPipe service.
|
||||
*/
|
||||
protected function getBigPipeService(FilterResponseEvent $event) {
|
||||
return $this->bigPipe;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -40,7 +40,7 @@ class NoBigPipeRouteAlterSubscriber implements EventSubscriberInterface {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
public static function getSubscribedEvents() {
|
||||
$events[RoutingEvents::ALTER][] = ['onRoutingRouteAlterSetNoBigPipe'];
|
||||
return $events;
|
||||
}
|
||||
|
|
|
@ -21,9 +21,133 @@ use Symfony\Component\HttpKernel\HttpKernelInterface;
|
|||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
|
||||
/**
|
||||
* The default BigPipe service.
|
||||
* Service for sending an HTML response in chunks (to get faster page loads).
|
||||
*
|
||||
* At a high level, BigPipe sends a HTML response in chunks:
|
||||
* 1. one chunk: everything until just before </body> — this contains BigPipe
|
||||
* placeholders for the personalized parts of the page. Hence this sends the
|
||||
* non-personalized parts of the page. Let's call it The Skeleton.
|
||||
* 2. N chunks: a <script> tag per BigPipe placeholder in The Skeleton.
|
||||
* 3. one chunk: </body> and everything after it.
|
||||
*
|
||||
* This is conceptually identical to Facebook's BigPipe (hence the name).
|
||||
*
|
||||
* @see https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919
|
||||
*
|
||||
* The major way in which Drupal differs from Facebook's implementation (and
|
||||
* others) is in its ability to automatically figure out which parts of the page
|
||||
* can benefit from BigPipe-style delivery. Drupal's render system has the
|
||||
* concept of "auto-placeholdering": content that is too dynamic is replaced
|
||||
* with a placeholder that can then be rendered at a later time. On top of that,
|
||||
* it also has the concept of "placeholder strategies": by default, placeholders
|
||||
* are replaced on the server side and the response is blocked on all of them
|
||||
* being replaced. But it's possible to add additional placeholder strategies.
|
||||
* BigPipe is just another placeholder strategy. Others could be ESI, AJAX …
|
||||
*
|
||||
* @see https://www.drupal.org/developing/api/8/render/arrays/cacheability/auto-placeholdering
|
||||
* @see \Drupal\Core\Render\PlaceholderGeneratorInterface::shouldAutomaticallyPlaceholder()
|
||||
* @see \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface
|
||||
* @see \Drupal\Core\Render\Placeholder\SingleFlushStrategy
|
||||
* @see \Drupal\big_pipe\Render\Placeholder\BigPipeStrategy
|
||||
*
|
||||
* There is also one noteworthy technical addition that Drupal makes. BigPipe as
|
||||
* described above, and as implemented by Facebook, can only work if JavaScript
|
||||
* is enabled. The BigPipe module also makes it possible to replace placeholders
|
||||
* using BigPipe in-situ, without JavaScript. This is not technically BigPipe at
|
||||
* all; it's just the use of multiple flushes. Since it is able to reuse much of
|
||||
* the logic though, we choose to call this "no-JS BigPipe".
|
||||
*
|
||||
* However, there is also a tangible benefit: some dynamic/expensive content is
|
||||
* not HTML, but for example a HTML attribute value (or part thereof). It's not
|
||||
* possible to efficiently replace such content using JavaScript, so "classic"
|
||||
* BigPipe is out of the question. For example: CSRF tokens in URLs.
|
||||
*
|
||||
* This allows us to use both no-JS BigPipe and "classic" BigPipe in the same
|
||||
* response to maximize the amount of content we can send as early as possible.
|
||||
*
|
||||
* Finally, a closer look at the implementation, and how it supports and reuses
|
||||
* existing Drupal concepts:
|
||||
* 1. BigPipe placeholders: 1 HtmlResponse + N embedded AjaxResponses.
|
||||
* - Before a BigPipe response is sent, it is just a HTML response that
|
||||
* contains BigPipe placeholders. Those placeholders look like
|
||||
* <span data-big-pipe-placeholder-id="…"></span>. JavaScript is used to
|
||||
* replace those placeholders.
|
||||
* Therefore these placeholders are actually sent to the client.
|
||||
* - The Skeleton of course has attachments, including most notably asset
|
||||
* libraries. And those we track in drupalSettings.ajaxPageState.libraries —
|
||||
* so that when we load new content through AJAX, we don't load the same
|
||||
* asset libraries again. A HTML page can have multiple AJAX responses, each
|
||||
* of which should take into account the combined AJAX page state of the
|
||||
* HTML document and all preceding AJAX responses.
|
||||
* - BigPipe does not make use of multiple AJAX requests/responses. It uses a
|
||||
* single HTML response. But it is a more long-lived one: The Skeleton is
|
||||
* sent first, the closing </body> tag is not yet sent, and the connection
|
||||
* is kept open. Whenever another BigPipe Placeholder is rendered, Drupal
|
||||
* sends (and so actually appends to the already-sent HTML) something like
|
||||
* <script type="application/vnd.drupal-ajax">[{"command":"settings","settings":{…}}, {"command":…}.
|
||||
* - So, for every BigPipe placeholder, we send such a <script
|
||||
* type="application/vnd.drupal-ajax"> tag. And the contents of that tag is
|
||||
* exactly like an AJAX response. The BigPipe module has JavaScript that
|
||||
* listens for these and applies them. Let's call it an Embedded AJAX
|
||||
* Response (since it is embedded in the HTML response). Now for the
|
||||
* interesting bit: each of those Embedded AJAX Responses must also take
|
||||
* into account the cumulative AJAX page state of the HTML document and all
|
||||
* preceding Embedded AJAX responses.
|
||||
* 2. No-JS BigPipe placeholders: 1 HtmlResponse + N embedded HtmlResponses.
|
||||
* - Before a BigPipe response is sent, it is just a HTML response that
|
||||
* contains no-JS BigPipe placeholders. Those placeholders can take two
|
||||
* different forms:
|
||||
* 1. <span data-big-pipe-nojs-placeholder-id="…"></span> if it's a
|
||||
* placeholder that will be replaced by HTML
|
||||
* 2. big_pipe_nojs_placeholder_attribute_safe:… if it's a placeholder
|
||||
* inside a HTML attribute, in which 1. would be invalid (angle brackets
|
||||
* are not allowed inside HTML attributes)
|
||||
* No-JS BigPipe placeholders are not replaced using JavaScript, they must
|
||||
* be replaced upon sending the BigPipe response. So, while the response is
|
||||
* being sent, upon encountering these placeholders, their corresponding
|
||||
* placeholder replacements are sent instead.
|
||||
* Therefore these placeholders are never actually sent to the client.
|
||||
* - See second bullet of point 1.
|
||||
* - No-JS BigPipe does not use multiple AJAX requests/responses. It uses a
|
||||
* single HTML response. But it is a more long-lived one: The Skeleton is
|
||||
* split into multiple parts, the separators are where the no-JS BigPipe
|
||||
* placeholders used to be. Whenever another no-JS BigPipe placeholder is
|
||||
* rendered, Drupal sends (and so actually appends to the already-sent HTML)
|
||||
* something like
|
||||
* <link rel="stylesheet" …><script …><content>.
|
||||
* - So, for every no-JS BigPipe placeholder, we send its associated CSS and
|
||||
* header JS that has not already been sent (the bottom JS is not yet sent,
|
||||
* so we can accumulate all of it and send it together at the end). This
|
||||
* ensures that the markup is rendered as it was originally intended: its
|
||||
* CSS and JS used to be blocking, and it still is. Let's call it an
|
||||
* Embedded HTML response. Each of those Embedded HTML Responses must also
|
||||
* take into account the cumulative AJAX page state of the HTML document and
|
||||
* all preceding Embedded HTML responses.
|
||||
* - Finally: any non-critical JavaScript associated with all Embedded HTML
|
||||
* Responses, i.e. any footer/bottom/non-header JavaScript, is loaded after
|
||||
* The Skeleton.
|
||||
*
|
||||
* Combining all of the above, when using both BigPipe placeholders and no-JS
|
||||
* BigPipe placeholders, we therefore send: 1 HtmlResponse + M Embedded HTML
|
||||
* Responses + N Embedded AJAX Responses. Schematically, we send these chunks:
|
||||
* 1. Byte zero until 1st no-JS placeholder: headers + <html><head /><span>…</span>
|
||||
* 2. 1st no-JS placeholder replacement: <link rel="stylesheet" …><script …><content>
|
||||
* 3. Content until 2nd no-JS placeholder: <span>…</span>
|
||||
* 4. 2nd no-JS placeholder replacement: <link rel="stylesheet" …><script …><content>
|
||||
* 5. Content until 3rd no-JS placeholder: <span>…</span>
|
||||
* 6. [… repeat until all no-JS placeholder replacements are sent …]
|
||||
* 7. Send content after last no-JS placeholder.
|
||||
* 8. Send script_bottom (markup to load bottom i.e. non-critical JS).
|
||||
* 9. 1st placeholder replacement: <script type="application/vnd.drupal-ajax">[{"command":"settings","settings":{…}}, {"command":…}
|
||||
* 10. 2nd placeholder replacement: <script type="application/vnd.drupal-ajax">[{"command":"settings","settings":{…}}, {"command":…}
|
||||
* 11. [… repeat until all placeholder replacements are sent …]
|
||||
* 12. Send </body> and everything after it.
|
||||
* 13. Terminate request/response cycle.
|
||||
*
|
||||
* @see \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber
|
||||
* @see \Drupal\big_pipe\Render\Placeholder\BigPipeStrategy
|
||||
*/
|
||||
class BigPipe implements BigPipeInterface {
|
||||
class BigPipe {
|
||||
|
||||
/**
|
||||
* The BigPipe placeholder replacements start signal.
|
||||
|
@ -107,9 +231,56 @@ class BigPipe implements BigPipeInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Performs tasks before sending content (and rendering placeholders).
|
||||
*/
|
||||
public function sendContent($content, array $attachments) {
|
||||
protected function performPreSendTasks() {
|
||||
// The content in the placeholders may depend on the session, and by the
|
||||
// time the response is sent (see index.php), the session is already
|
||||
// closed. Reopen it for the duration that we are rendering placeholders.
|
||||
$this->session->start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs tasks after sending content (and rendering placeholders).
|
||||
*/
|
||||
protected function performPostSendTasks() {
|
||||
// Close the session again.
|
||||
$this->session->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a chunk.
|
||||
*
|
||||
* @param string|\Drupal\Core\Render\HtmlResponse $chunk
|
||||
* The string or response to append. String if there's no cacheability
|
||||
* metadata or attachments to merge.
|
||||
*/
|
||||
protected function sendChunk($chunk) {
|
||||
assert(is_string($chunk) || $chunk instanceof HtmlResponse);
|
||||
if ($chunk instanceof HtmlResponse) {
|
||||
print $chunk->getContent();
|
||||
}
|
||||
else {
|
||||
print $chunk;
|
||||
}
|
||||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTML response in chunks using the BigPipe technique.
|
||||
*
|
||||
* @param \Drupal\big_pipe\Render\BigPipeResponse $response
|
||||
* The BigPipe response to send.
|
||||
*
|
||||
* @internal
|
||||
* This method should only be invoked by
|
||||
* \Drupal\big_pipe\Render\BigPipeResponse, which is itself an internal
|
||||
* class.
|
||||
*/
|
||||
public function sendContent(BigPipeResponse $response) {
|
||||
$content = $response->getContent();
|
||||
$attachments = $response->getAttachments();
|
||||
|
||||
// First, gather the BigPipe placeholders that must be replaced.
|
||||
$placeholders = isset($attachments['big_pipe_placeholders']) ? $attachments['big_pipe_placeholders'] : [];
|
||||
$nojs_placeholders = isset($attachments['big_pipe_nojs_placeholders']) ? $attachments['big_pipe_nojs_placeholders'] : [];
|
||||
|
@ -121,10 +292,7 @@ class BigPipe implements BigPipeInterface {
|
|||
$cumulative_assets = AttachedAssets::createFromRenderArray(['#attached' => $attachments]);
|
||||
$cumulative_assets->setAlreadyLoadedLibraries($attachments['library']);
|
||||
|
||||
// The content in the placeholders may depend on the session, and by the
|
||||
// time the response is sent (see index.php), the session is already closed.
|
||||
// Reopen it for the duration that we are rendering placeholders.
|
||||
$this->session->start();
|
||||
$this->performPreSendTasks();
|
||||
|
||||
// Find the closing </body> tag and get the strings before and after. But be
|
||||
// careful to use the latest occurrence of the string "</body>", to ensure
|
||||
|
@ -137,10 +305,7 @@ class BigPipe implements BigPipeInterface {
|
|||
$this->sendPlaceholders($placeholders, $this->getPlaceholderOrder($pre_body, $placeholders), $cumulative_assets);
|
||||
$this->sendPostBody($post_body);
|
||||
|
||||
// Close the session again.
|
||||
$this->session->save();
|
||||
|
||||
return $this;
|
||||
$this->performPostSendTasks();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,8 +323,7 @@ class BigPipe implements BigPipeInterface {
|
|||
// If there are no no-JS BigPipe placeholders, we can send the pre-</body>
|
||||
// part of the page immediately.
|
||||
if (empty($no_js_placeholders)) {
|
||||
print $pre_body;
|
||||
flush();
|
||||
$this->sendChunk($pre_body);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -202,8 +366,7 @@ class BigPipe implements BigPipeInterface {
|
|||
$scripts_bottom = $html_response->getContent();
|
||||
}
|
||||
|
||||
print $scripts_bottom;
|
||||
flush();
|
||||
$this->sendChunk($scripts_bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -244,8 +407,7 @@ class BigPipe implements BigPipeInterface {
|
|||
// between placeholders and it must be printed & flushed immediately. The
|
||||
// rest of the logic in the loop handles the placeholders.
|
||||
if (!isset($no_js_placeholders[$fragment])) {
|
||||
print $fragment;
|
||||
flush();
|
||||
$this->sendChunk($fragment);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -253,8 +415,7 @@ class BigPipe implements BigPipeInterface {
|
|||
// this is the second occurrence, we can skip all calculations and just
|
||||
// send the same content.
|
||||
if ($placeholder_occurrences[$fragment] > 1 && isset($multi_occurrence_placeholders_content[$fragment])) {
|
||||
print $multi_occurrence_placeholders_content[$fragment];
|
||||
flush();
|
||||
$this->sendChunk($multi_occurrence_placeholders_content[$fragment]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -324,8 +485,7 @@ class BigPipe implements BigPipeInterface {
|
|||
|
||||
|
||||
// Send this embedded HTML response.
|
||||
print $html_response->getContent();
|
||||
flush();
|
||||
$this->sendChunk($html_response);
|
||||
|
||||
// Another placeholder was rendered and sent, track the set of asset
|
||||
// libraries sent so far. Any new settings also need to be tracked, so
|
||||
|
@ -369,10 +529,7 @@ class BigPipe implements BigPipeInterface {
|
|||
}
|
||||
|
||||
// Send the start signal.
|
||||
print "\n";
|
||||
print static::START_SIGNAL;
|
||||
print "\n";
|
||||
flush();
|
||||
$this->sendChunk("\n" . static::START_SIGNAL . "\n");
|
||||
|
||||
// A BigPipe response consists of a HTML response plus multiple embedded
|
||||
// AJAX responses. To process the attachments of those AJAX responses, we
|
||||
|
@ -444,8 +601,7 @@ class BigPipe implements BigPipeInterface {
|
|||
$json
|
||||
</script>
|
||||
EOF;
|
||||
print $output;
|
||||
flush();
|
||||
$this->sendChunk($output);
|
||||
|
||||
// Another placeholder was rendered and sent, track the set of asset
|
||||
// libraries sent so far. Any new settings are already sent; we don't need
|
||||
|
@ -456,10 +612,7 @@ EOF;
|
|||
}
|
||||
|
||||
// Send the stop signal.
|
||||
print "\n";
|
||||
print static::STOP_SIGNAL;
|
||||
print "\n";
|
||||
flush();
|
||||
$this->sendChunk("\n" . static::STOP_SIGNAL . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -479,8 +632,28 @@ EOF;
|
|||
*/
|
||||
protected function filterEmbeddedResponse(Request $fake_request, Response $embedded_response) {
|
||||
assert('$embedded_response instanceof \Drupal\Core\Render\HtmlResponse || $embedded_response instanceof \Drupal\Core\Ajax\AjaxResponse');
|
||||
$this->requestStack->push($fake_request);
|
||||
$event = new FilterResponseEvent($this->httpKernel, $fake_request, HttpKernelInterface::SUB_REQUEST, $embedded_response);
|
||||
return $this->filterResponse($fake_request, HttpKernelInterface::SUB_REQUEST, $embedded_response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the given response.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The request for which a response is being sent.
|
||||
* @param int $request_type
|
||||
* The request type. Can either be
|
||||
* \Symfony\Component\HttpKernel\HttpKernelInterface::MASTER_REQUEST or
|
||||
* \Symfony\Component\HttpKernel\HttpKernelInterface::SUB_REQUEST.
|
||||
* @param \Symfony\Component\HttpFoundation\Response $response
|
||||
* The response to filter.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
* The filtered response.
|
||||
*/
|
||||
protected function filterResponse(Request $request, $request_type, Response $response) {
|
||||
assert('$request_type === \Symfony\Component\HttpKernel\HttpKernelInterface::MASTER_REQUEST || $request_type === \Symfony\Component\HttpKernel\HttpKernelInterface::SUB_REQUEST');
|
||||
$this->requestStack->push($request);
|
||||
$event = new FilterResponseEvent($this->httpKernel, $request, $request_type, $response);
|
||||
$this->eventDispatcher->dispatch(KernelEvents::RESPONSE, $event);
|
||||
$filtered_response = $event->getResponse();
|
||||
$this->requestStack->pop();
|
||||
|
@ -494,9 +667,7 @@ EOF;
|
|||
* The HTML response's content after the closing </body> tag.
|
||||
*/
|
||||
protected function sendPostBody($post_body) {
|
||||
print '</body>';
|
||||
print $post_body;
|
||||
flush();
|
||||
$this->sendChunk('</body>' . $post_body);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -545,12 +716,12 @@ EOF;
|
|||
* only keep the first occurrence.
|
||||
*/
|
||||
protected function getPlaceholderOrder($html, $placeholders) {
|
||||
$fragments = explode('<div data-big-pipe-placeholder-id="', $html);
|
||||
$fragments = explode('<span data-big-pipe-placeholder-id="', $html);
|
||||
array_shift($fragments);
|
||||
$placeholder_ids = [];
|
||||
|
||||
foreach ($fragments as $fragment) {
|
||||
$t = explode('"></div>', $fragment, 2);
|
||||
$t = explode('"></span>', $fragment, 2);
|
||||
$placeholder_id = $t[0];
|
||||
$placeholder_ids[] = $placeholder_id;
|
||||
}
|
||||
|
|
|
@ -1,150 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\big_pipe\Render;
|
||||
|
||||
/**
|
||||
* Interface for sending an HTML response in chunks (to get faster page loads).
|
||||
*
|
||||
* At a high level, BigPipe sends a HTML response in chunks:
|
||||
* 1. one chunk: everything until just before </body> — this contains BigPipe
|
||||
* placeholders for the personalized parts of the page. Hence this sends the
|
||||
* non-personalized parts of the page. Let's call it The Skeleton.
|
||||
* 2. N chunks: a <script> tag per BigPipe placeholder in The Skeleton.
|
||||
* 3. one chunk: </body> and everything after it.
|
||||
*
|
||||
* This is conceptually identical to Facebook's BigPipe (hence the name).
|
||||
*
|
||||
* @see https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919
|
||||
*
|
||||
* The major way in which Drupal differs from Facebook's implementation (and
|
||||
* others) is in its ability to automatically figure out which parts of the page
|
||||
* can benefit from BigPipe-style delivery. Drupal's render system has the
|
||||
* concept of "auto-placeholdering": content that is too dynamic is replaced
|
||||
* with a placeholder that can then be rendered at a later time. On top of that,
|
||||
* it also has the concept of "placeholder strategies": by default, placeholders
|
||||
* are replaced on the server side and the response is blocked on all of them
|
||||
* being replaced. But it's possible to add additional placeholder strategies.
|
||||
* BigPipe is just another placeholder strategy. Others could be ESI, AJAX …
|
||||
*
|
||||
* @see https://www.drupal.org/developing/api/8/render/arrays/cacheability/auto-placeholdering
|
||||
* @see \Drupal\Core\Render\PlaceholderGeneratorInterface::shouldAutomaticallyPlaceholder()
|
||||
* @see \Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface
|
||||
* @see \Drupal\Core\Render\Placeholder\SingleFlushStrategy
|
||||
* @see \Drupal\big_pipe\Render\Placeholder\BigPipeStrategy
|
||||
*
|
||||
* There is also one noteworthy technical addition that Drupal makes. BigPipe as
|
||||
* described above, and as implemented by Facebook, can only work if JavaScript
|
||||
* is enabled. The BigPipe module also makes it possible to replace placeholders
|
||||
* using BigPipe in-situ, without JavaScript. This is not technically BigPipe at
|
||||
* all; it's just the use of multiple flushes. Since it is able to reuse much of
|
||||
* the logic though, we choose to call this "no-JS BigPipe".
|
||||
*
|
||||
* However, there is also a tangible benefit: some dynamic/expensive content is
|
||||
* not HTML, but for example a HTML attribute value (or part thereof). It's not
|
||||
* possible to efficiently replace such content using JavaScript, so "classic"
|
||||
* BigPipe is out of the question. For example: CSRF tokens in URLs.
|
||||
*
|
||||
* This allows us to use both no-JS BigPipe and "classic" BigPipe in the same
|
||||
* response to maximize the amount of content we can send as early as possible.
|
||||
*
|
||||
* Finally, a closer look at the implementation, and how it supports and reuses
|
||||
* existing Drupal concepts:
|
||||
* 1. BigPipe placeholders: 1 HtmlResponse + N embedded AjaxResponses.
|
||||
* - Before a BigPipe response is sent, it is just a HTML response that
|
||||
* contains BigPipe placeholders. Those placeholders look like
|
||||
* <div data-big-pipe-placeholder-id="…"></div>. JavaScript is used to
|
||||
* replace those placeholders.
|
||||
* Therefore these placeholders are actually sent to the client.
|
||||
* - The Skeleton of course has attachments, including most notably asset
|
||||
* libraries. And those we track in drupalSettings.ajaxPageState.libraries —
|
||||
* so that when we load new content through AJAX, we don't load the same
|
||||
* asset libraries again. A HTML page can have multiple AJAX responses, each
|
||||
* of which should take into account the combined AJAX page state of the
|
||||
* HTML document and all preceding AJAX responses.
|
||||
* - BigPipe does not make use of multiple AJAX requests/responses. It uses a
|
||||
* single HTML response. But it is a more long-lived one: The Skeleton is
|
||||
* sent first, the closing </body> tag is not yet sent, and the connection
|
||||
* is kept open. Whenever another BigPipe Placeholder is rendered, Drupal
|
||||
* sends (and so actually appends to the already-sent HTML) something like
|
||||
* <script type="application/vnd.drupal-ajax">[{"command":"settings","settings":{…}}, {"command":…}.
|
||||
* - So, for every BigPipe placeholder, we send such a <script
|
||||
* type="application/vnd.drupal-ajax"> tag. And the contents of that tag is
|
||||
* exactly like an AJAX response. The BigPipe module has JavaScript that
|
||||
* listens for these and applies them. Let's call it an Embedded AJAX
|
||||
* Response (since it is embedded in the HTML response). Now for the
|
||||
* interesting bit: each of those Embedded AJAX Responses must also take
|
||||
* into account the cumulative AJAX page state of the HTML document and all
|
||||
* preceding Embedded AJAX responses.
|
||||
* 2. No-JS BigPipe placeholders: 1 HtmlResponse + N embedded HtmlResponses.
|
||||
* - Before a BigPipe response is sent, it is just a HTML response that
|
||||
* contains no-JS BigPipe placeholders. Those placeholders can take two
|
||||
* different forms:
|
||||
* 1. <div data-big-pipe-nojs-placeholder-id="…"></div> if it's a
|
||||
* placeholder that will be replaced by HTML
|
||||
* 2. big_pipe_nojs_placeholder_attribute_safe:… if it's a placeholder
|
||||
* inside a HTML attribute, in which 1. would be invalid (angle brackets
|
||||
* are not allowed inside HTML attributes)
|
||||
* No-JS BigPipe placeholders are not replaced using JavaScript, they must
|
||||
* be replaced upon sending the BigPipe response. So, while the response is
|
||||
* being sent, upon encountering these placeholders, their corresponding
|
||||
* placeholder replacements are sent instead.
|
||||
* Therefore these placeholders are never actually sent to the client.
|
||||
* - See second bullet of point 1.
|
||||
* - No-JS BigPipe does not use multiple AJAX requests/responses. It uses a
|
||||
* single HTML response. But it is a more long-lived one: The Skeleton is
|
||||
* split into multiple parts, the separators are where the no-JS BigPipe
|
||||
* placeholders used to be. Whenever another no-JS BigPipe placeholder is
|
||||
* rendered, Drupal sends (and so actually appends to the already-sent HTML)
|
||||
* something like
|
||||
* <link rel="stylesheet" …><script …><content>.
|
||||
* - So, for every no-JS BigPipe placeholder, we send its associated CSS and
|
||||
* header JS that has not already been sent (the bottom JS is not yet sent,
|
||||
* so we can accumulate all of it and send it together at the end). This
|
||||
* ensures that the markup is rendered as it was originally intended: its
|
||||
* CSS and JS used to be blocking, and it still is. Let's call it an
|
||||
* Embedded HTML response. Each of those Embedded HTML Responses must also
|
||||
* take into account the cumulative AJAX page state of the HTML document and
|
||||
* all preceding Embedded HTML responses.
|
||||
* - Finally: any non-critical JavaScript associated with all Embedded HTML
|
||||
* Responses, i.e. any footer/bottom/non-header JavaScript, is loaded after
|
||||
* The Skeleton.
|
||||
*
|
||||
* Combining all of the above, when using both BigPipe placeholders and no-JS
|
||||
* BigPipe placeholders, we therefore send: 1 HtmlResponse + M Embedded HTML
|
||||
* Responses + N Embedded AJAX Responses. Schematically, we send these chunks:
|
||||
* 1. Byte zero until 1st no-JS placeholder: headers + <html><head /><div>…</div>
|
||||
* 2. 1st no-JS placeholder replacement: <link rel="stylesheet" …><script …><content>
|
||||
* 3. Content until 2nd no-JS placeholder: <div>…</div>
|
||||
* 4. 2nd no-JS placeholder replacement: <link rel="stylesheet" …><script …><content>
|
||||
* 5. Content until 3rd no-JS placeholder: <div>…</div>
|
||||
* 6. [… repeat until all no-JS placeholder replacements are sent …]
|
||||
* 7. Send content after last no-JS placeholder.
|
||||
* 8. Send script_bottom (markup to load bottom i.e. non-critical JS).
|
||||
* 9. 1st placeholder replacement: <script type="application/vnd.drupal-ajax">[{"command":"settings","settings":{…}}, {"command":…}
|
||||
* 10. 2nd placeholder replacement: <script type="application/vnd.drupal-ajax">[{"command":"settings","settings":{…}}, {"command":…}
|
||||
* 11. [… repeat until all placeholder replacements are sent …]
|
||||
* 12. Send </body> and everything after it.
|
||||
* 13. Terminate request/response cycle.
|
||||
*
|
||||
* @see \Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber
|
||||
* @see \Drupal\big_pipe\Render\Placeholder\BigPipeStrategy
|
||||
*/
|
||||
interface BigPipeInterface {
|
||||
|
||||
/**
|
||||
* Sends an HTML response in chunks using the BigPipe technique.
|
||||
*
|
||||
* @param string $content
|
||||
* The HTML response content to send.
|
||||
* @param array $attachments
|
||||
* The HTML response's attachments.
|
||||
*
|
||||
* @internal
|
||||
* This method should only be invoked by
|
||||
* \Drupal\big_pipe\Render\BigPipeResponse, which is itself an internal
|
||||
* class. Furthermore, the signature of this method will change in
|
||||
* https://www.drupal.org/node/2657684.
|
||||
*/
|
||||
public function sendContent($content, array $attachments);
|
||||
|
||||
}
|
|
@ -11,7 +11,7 @@ use Drupal\Core\Render\HtmlResponse;
|
|||
* it makes the content inaccessible (hidden behind a callback), which means no
|
||||
* middlewares are able to modify the content anymore.
|
||||
*
|
||||
* @see \Drupal\big_pipe\Render\BigPipeInterface
|
||||
* @see \Drupal\big_pipe\Render\BigPipe
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution until a generic response emitter interface is
|
||||
|
@ -23,17 +23,85 @@ class BigPipeResponse extends HtmlResponse {
|
|||
/**
|
||||
* The BigPipe service.
|
||||
*
|
||||
* @var \Drupal\big_pipe\Render\BigPipeInterface
|
||||
* @var \Drupal\big_pipe\Render\BigPipe
|
||||
*/
|
||||
protected $bigPipe;
|
||||
|
||||
/**
|
||||
* The original HTML response.
|
||||
*
|
||||
* Still contains placeholders. Its cacheability metadata and attachments are
|
||||
* for everything except the placeholders (since those are not yet rendered).
|
||||
*
|
||||
* @see \Drupal\Core\Render\StreamedResponseInterface
|
||||
* @see ::getStreamedResponse()
|
||||
*
|
||||
* @var \Drupal\Core\Render\HtmlResponse
|
||||
*/
|
||||
protected $originalHtmlResponse;
|
||||
|
||||
/**
|
||||
* Constructs a new BigPipeResponse.
|
||||
*
|
||||
* @param \Drupal\Core\Render\HtmlResponse $response
|
||||
* The original HTML response.
|
||||
*/
|
||||
public function __construct(HtmlResponse $response) {
|
||||
parent::__construct('', $response->getStatusCode(), []);
|
||||
|
||||
$this->originalHtmlResponse = $response;
|
||||
|
||||
$this->populateBasedOnOriginalHtmlResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original HTML response.
|
||||
*
|
||||
* @return \Drupal\Core\Render\HtmlResponse
|
||||
* The original HTML response.
|
||||
*/
|
||||
public function getOriginalHtmlResponse() {
|
||||
return $this->originalHtmlResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates this BigPipeResponse object based on the original HTML response.
|
||||
*/
|
||||
protected function populateBasedOnOriginalHtmlResponse() {
|
||||
// Clone the HtmlResponse's data into the new BigPipeResponse.
|
||||
$this->headers = clone $this->originalHtmlResponse->headers;
|
||||
$this
|
||||
->setStatusCode($this->originalHtmlResponse->getStatusCode())
|
||||
->setContent($this->originalHtmlResponse->getContent())
|
||||
->setAttachments($this->originalHtmlResponse->getAttachments())
|
||||
->addCacheableDependency($this->originalHtmlResponse->getCacheableMetadata());
|
||||
|
||||
// A BigPipe response can never be cached, because it is intended for a
|
||||
// single user.
|
||||
// @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.1
|
||||
$this->setPrivate();
|
||||
|
||||
// Inform surrogates how they should handle BigPipe responses:
|
||||
// - "no-store" specifies that the response should not be stored in cache;
|
||||
// it is only to be used for the original request
|
||||
// - "content" identifies what processing surrogates should perform on the
|
||||
// response before forwarding it. We send, "BigPipe/1.0", which surrogates
|
||||
// should not process at all, and in fact, they should not even buffer it
|
||||
// at all.
|
||||
// @see http://www.w3.org/TR/edge-arch/
|
||||
$this->headers->set('Surrogate-Control', 'no-store, content="BigPipe/1.0"');
|
||||
|
||||
// Add header to support streaming on NGINX + php-fpm (nginx >= 1.5.6).
|
||||
$this->headers->set('X-Accel-Buffering', 'no');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the BigPipe service to use.
|
||||
*
|
||||
* @param \Drupal\big_pipe\Render\BigPipeInterface $big_pipe
|
||||
* @param \Drupal\big_pipe\Render\BigPipe $big_pipe
|
||||
* The BigPipe service.
|
||||
*/
|
||||
public function setBigPipeService(BigPipeInterface $big_pipe) {
|
||||
public function setBigPipeService(BigPipe $big_pipe) {
|
||||
$this->bigPipe = $big_pipe;
|
||||
}
|
||||
|
||||
|
@ -41,7 +109,12 @@ class BigPipeResponse extends HtmlResponse {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function sendContent() {
|
||||
$this->bigPipe->sendContent($this->content, $this->getAttachments());
|
||||
$this->bigPipe->sendContent($this);
|
||||
|
||||
// All BigPipe placeholders are processed, so update this response's
|
||||
// attachments.
|
||||
unset($this->attachments['big_pipe_placeholders']);
|
||||
unset($this->attachments['big_pipe_nojs_placeholders']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
|
|||
* Processes attachments of HTML responses with BigPipe enabled.
|
||||
*
|
||||
* @see \Drupal\Core\Render\HtmlResponseAttachmentsProcessor
|
||||
* @see \Drupal\big_pipe\Render\BigPipeInterface
|
||||
* @see \Drupal\big_pipe\Render\BigPipe
|
||||
*/
|
||||
class BigPipeResponseAttachmentsProcessor extends HtmlResponseAttachmentsProcessor {
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Drupal\big_pipe\Render\Placeholder;
|
||||
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Render\Placeholder\PlaceholderStrategyInterface;
|
||||
|
@ -55,7 +56,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
|
|||
* See \Drupal\big_pipe\Render\BigPipe for detailed documentation on how those
|
||||
* different placeholders are actually replaced.
|
||||
*
|
||||
* @see \Drupal\big_pipe\Render\BigPipeInterface
|
||||
* @see \Drupal\big_pipe\Render\BigPipe
|
||||
*/
|
||||
class BigPipeStrategy implements PlaceholderStrategyInterface {
|
||||
|
||||
|
@ -149,7 +150,7 @@ class BigPipeStrategy implements PlaceholderStrategyInterface {
|
|||
// @see \Drupal\Core\Access\RouteProcessorCsrf::renderPlaceholderCsrfToken()
|
||||
// @see \Drupal\Core\Form\FormBuilder::renderFormTokenPlaceholder()
|
||||
// @see \Drupal\Core\Form\FormBuilder::renderPlaceholderFormAction()
|
||||
if ($placeholder[0] !== '<' || $placeholder !== Html::normalize($placeholder)) {
|
||||
if (static::placeholderIsAttributeSafe($placeholder)) {
|
||||
$overridden_placeholders[$placeholder] = static::createBigPipeNoJsPlaceholder($placeholder, $placeholder_elements, TRUE);
|
||||
}
|
||||
else {
|
||||
|
@ -168,6 +169,21 @@ class BigPipeStrategy implements PlaceholderStrategyInterface {
|
|||
return $overridden_placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given placeholder is attribute-safe or not.
|
||||
*
|
||||
* @param string $placeholder
|
||||
* A placeholder.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the placeholder is safe for use in a HTML attribute (in case it's
|
||||
* a placeholder for a HTML attribute value or a subset of it).
|
||||
*/
|
||||
protected static function placeholderIsAttributeSafe($placeholder) {
|
||||
assert('is_string($placeholder)');
|
||||
return $placeholder[0] !== '<' || $placeholder !== Html::normalize($placeholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a BigPipe JS placeholder.
|
||||
*
|
||||
|
@ -183,7 +199,7 @@ class BigPipeStrategy implements PlaceholderStrategyInterface {
|
|||
$big_pipe_placeholder_id = static::generateBigPipePlaceholderId($original_placeholder, $placeholder_render_array);
|
||||
|
||||
return [
|
||||
'#markup' => '<div data-big-pipe-placeholder-id="' . Html::escape($big_pipe_placeholder_id) . '"></div>',
|
||||
'#markup' => '<span data-big-pipe-placeholder-id="' . Html::escape($big_pipe_placeholder_id) . '"></span>',
|
||||
'#cache' => [
|
||||
'max-age' => 0,
|
||||
'contexts' => [
|
||||
|
@ -221,7 +237,7 @@ class BigPipeStrategy implements PlaceholderStrategyInterface {
|
|||
*/
|
||||
protected static function createBigPipeNoJsPlaceholder($original_placeholder, array $placeholder_render_array, $placeholder_must_be_attribute_safe = FALSE) {
|
||||
if (!$placeholder_must_be_attribute_safe) {
|
||||
$big_pipe_placeholder = '<div data-big-pipe-nojs-placeholder-id="' . Html::escape(static::generateBigPipePlaceholderId($original_placeholder, $placeholder_render_array)) . '"></div>';
|
||||
$big_pipe_placeholder = '<span data-big-pipe-nojs-placeholder-id="' . Html::escape(static::generateBigPipePlaceholderId($original_placeholder, $placeholder_render_array)) . '"></span>';
|
||||
}
|
||||
else {
|
||||
$big_pipe_placeholder = 'big_pipe_nojs_placeholder_attribute_safe:' . Html::escape($original_placeholder);
|
||||
|
@ -260,7 +276,7 @@ class BigPipeStrategy implements PlaceholderStrategyInterface {
|
|||
if (isset($placeholder_render_array['#lazy_builder'])) {
|
||||
$callback = $placeholder_render_array['#lazy_builder'][0];
|
||||
$arguments = $placeholder_render_array['#lazy_builder'][1];
|
||||
$token = hash('crc32b', serialize($placeholder_render_array));
|
||||
$token = Crypt::hashBase64(serialize($placeholder_render_array));
|
||||
return UrlHelper::buildQuery(['callback' => $callback, 'args' => $arguments, 'token' => $token]);
|
||||
}
|
||||
// When the placeholder's render array is not using a #lazy_builder,
|
||||
|
|
|
@ -51,7 +51,7 @@ class BigPipePlaceholderTestCases {
|
|||
// 1. Real-world example of HTML placeholder.
|
||||
$status_messages = new BigPipePlaceholderTestCase(
|
||||
['#type' => 'status_messages'],
|
||||
'<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
|
||||
'<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></drupal-render-placeholder>',
|
||||
[
|
||||
'#lazy_builder' => [
|
||||
'Drupal\Core\Render\Element\StatusMessages::renderMessages',
|
||||
|
@ -59,29 +59,29 @@ class BigPipePlaceholderTestCases {
|
|||
],
|
||||
]
|
||||
);
|
||||
$status_messages->bigPipePlaceholderId = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e';
|
||||
$status_messages->bigPipePlaceholderId = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
|
||||
$status_messages->bigPipePlaceholderRenderArray = [
|
||||
'#markup' => '<div data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e"></div>',
|
||||
'#markup' => '<span data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>',
|
||||
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
|
||||
'#attached' => [
|
||||
'library' => ['big_pipe/big_pipe'],
|
||||
'drupalSettings' => [
|
||||
'bigPipePlaceholderIds' => [
|
||||
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e' => TRUE,
|
||||
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => TRUE,
|
||||
],
|
||||
],
|
||||
'big_pipe_placeholders' => [
|
||||
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e' => $status_messages->placeholderRenderArray,
|
||||
'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA' => $status_messages->placeholderRenderArray,
|
||||
],
|
||||
],
|
||||
];
|
||||
$status_messages->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e"></div>';
|
||||
$status_messages->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>';
|
||||
$status_messages->bigPipeNoJsPlaceholderRenderArray = [
|
||||
'#markup' => '<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e"></div>',
|
||||
'#markup' => '<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>',
|
||||
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
|
||||
'#attached' => [
|
||||
'big_pipe_nojs_placeholders' => [
|
||||
'<div data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e"></div>' => $status_messages->placeholderRenderArray,
|
||||
'<span data-big-pipe-nojs-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"></span>' => $status_messages->placeholderRenderArray,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -109,7 +109,7 @@ class BigPipePlaceholderTestCases {
|
|||
[
|
||||
'command' => 'insert',
|
||||
'method' => 'replaceWith',
|
||||
'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e"]',
|
||||
'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA"]',
|
||||
'data' => "\n" . ' <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n ",
|
||||
'settings' => NULL,
|
||||
],
|
||||
|
@ -225,7 +225,7 @@ class BigPipePlaceholderTestCases {
|
|||
);
|
||||
$current_time->bigPipePlaceholderId = 'timecurrent-timetime';
|
||||
$current_time->bigPipePlaceholderRenderArray = [
|
||||
'#markup' => '<div data-big-pipe-placeholder-id="timecurrent-timetime"></div>',
|
||||
'#markup' => '<span data-big-pipe-placeholder-id="timecurrent-timetime"></span>',
|
||||
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
|
||||
'#attached' => [
|
||||
'library' => ['big_pipe/big_pipe'],
|
||||
|
@ -248,13 +248,13 @@ class BigPipePlaceholderTestCases {
|
|||
'settings' => NULL,
|
||||
],
|
||||
];
|
||||
$current_time->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></div>';
|
||||
$current_time->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></span>';
|
||||
$current_time->bigPipeNoJsPlaceholderRenderArray = [
|
||||
'#markup' => '<div data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></div>',
|
||||
'#markup' => '<span data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></span>',
|
||||
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
|
||||
'#attached' => [
|
||||
'big_pipe_nojs_placeholders' => [
|
||||
'<div data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></div>' => $current_time->placeholderRenderArray,
|
||||
'<span data-big-pipe-nojs-placeholder-id="timecurrent-timetime"></span>' => $current_time->placeholderRenderArray,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -267,29 +267,29 @@ class BigPipePlaceholderTestCases {
|
|||
'#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
|
||||
'#create_placeholder' => TRUE,
|
||||
],
|
||||
'<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::exception" arguments="0=llamas&1=suck" token="68a75f1a"></drupal-render-placeholder>',
|
||||
'<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::exception" arguments="0=llamas&1=suck" token="uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></drupal-render-placeholder>',
|
||||
[
|
||||
'#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::exception', ['llamas', 'suck']],
|
||||
]
|
||||
);
|
||||
$exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=68a75f1a';
|
||||
$exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU';
|
||||
$exception->bigPipePlaceholderRenderArray = [
|
||||
'#markup' => '<div data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=68a75f1a"></div>',
|
||||
'#markup' => '<span data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></span>',
|
||||
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
|
||||
'#attached' => [
|
||||
'library' => ['big_pipe/big_pipe'],
|
||||
'drupalSettings' => [
|
||||
'bigPipePlaceholderIds' => [
|
||||
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=68a75f1a' => TRUE,
|
||||
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => TRUE,
|
||||
],
|
||||
],
|
||||
'big_pipe_placeholders' => [
|
||||
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=68a75f1a' => $exception->placeholderRenderArray,
|
||||
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU' => $exception->placeholderRenderArray,
|
||||
],
|
||||
],
|
||||
];
|
||||
$exception->embeddedAjaxResponseCommands = NULL;
|
||||
$exception->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=68a75f1a"></div>';
|
||||
$exception->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3Aexception&args[0]=llamas&args[1]=suck&token=uhKFNfT4eF449_W-kDQX8E5z4yHyt0-nSHUlwaGAQeU"></span>';
|
||||
$exception->bigPipeNoJsPlaceholderRenderArray = [
|
||||
'#markup' => $exception->bigPipeNoJsPlaceholder,
|
||||
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
|
||||
|
@ -307,29 +307,29 @@ class BigPipePlaceholderTestCases {
|
|||
'#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
|
||||
'#create_placeholder' => TRUE,
|
||||
],
|
||||
'<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::responseException" arguments="" token="2a9bd022"></drupal-render-placeholder>',
|
||||
'<drupal-render-placeholder callback="\Drupal\big_pipe_test\BigPipeTestController::responseException" arguments="" token="PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU"></drupal-render-placeholder>',
|
||||
[
|
||||
'#lazy_builder' => ['\Drupal\big_pipe_test\BigPipeTestController::responseException', []],
|
||||
]
|
||||
);
|
||||
$embedded_response_exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=2a9bd022';
|
||||
$embedded_response_exception->bigPipePlaceholderId = 'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU';
|
||||
$embedded_response_exception->bigPipePlaceholderRenderArray = [
|
||||
'#markup' => '<div data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=2a9bd022"></div>',
|
||||
'#markup' => '<span data-big-pipe-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU"></span>',
|
||||
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
|
||||
'#attached' => [
|
||||
'library' => ['big_pipe/big_pipe'],
|
||||
'drupalSettings' => [
|
||||
'bigPipePlaceholderIds' => [
|
||||
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=2a9bd022' => TRUE,
|
||||
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU' => TRUE,
|
||||
],
|
||||
],
|
||||
'big_pipe_placeholders' => [
|
||||
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=2a9bd022' => $embedded_response_exception->placeholderRenderArray,
|
||||
'callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU' => $embedded_response_exception->placeholderRenderArray,
|
||||
],
|
||||
],
|
||||
];
|
||||
$embedded_response_exception->embeddedAjaxResponseCommands = NULL;
|
||||
$embedded_response_exception->bigPipeNoJsPlaceholder = '<div data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=2a9bd022"></div>';
|
||||
$embedded_response_exception->bigPipeNoJsPlaceholder = '<span data-big-pipe-nojs-placeholder-id="callback=%5CDrupal%5Cbig_pipe_test%5CBigPipeTestController%3A%3AresponseException&&token=PxOHfS_QL-T01NjBgu7Z7I04tIwMp6La5vM-mVxezbU"></span>';
|
||||
$embedded_response_exception->bigPipeNoJsPlaceholderRenderArray = [
|
||||
'#markup' => $embedded_response_exception->bigPipeNoJsPlaceholder,
|
||||
'#cache' => $cacheability_depends_on_session_and_nojs_cookie,
|
||||
|
|
|
@ -158,7 +158,9 @@ class BigPipeTest extends WebTestBase {
|
|||
|
||||
$this->drupalGet(Url::fromRoute('big_pipe_test'));
|
||||
$this->assertBigPipeResponseHeadersPresent();
|
||||
$this->assertNoCacheTag('cache_tag_set_in_lazy_builder');
|
||||
|
||||
$this->setCsrfTokenSeedInTestEnvironment();
|
||||
$cases = $this->getTestCases();
|
||||
$this->assertBigPipeNoJsPlaceholders([
|
||||
$cases['edge_case__invalid_html']->bigPipeNoJsPlaceholder => $cases['edge_case__invalid_html']->embeddedHtmlResponse,
|
||||
|
@ -236,7 +238,9 @@ class BigPipeTest extends WebTestBase {
|
|||
|
||||
$this->drupalGet(Url::fromRoute('big_pipe_test'));
|
||||
$this->assertBigPipeResponseHeadersPresent();
|
||||
$this->assertNoCacheTag('cache_tag_set_in_lazy_builder');
|
||||
|
||||
$this->setCsrfTokenSeedInTestEnvironment();
|
||||
$cases = $this->getTestCases();
|
||||
$this->assertBigPipeNoJsPlaceholders([
|
||||
$cases['edge_case__invalid_html']->bigPipeNoJsPlaceholder => $cases['edge_case__invalid_html']->embeddedHtmlResponse,
|
||||
|
@ -289,7 +293,7 @@ class BigPipeTest extends WebTestBase {
|
|||
// @see performMetaRefresh()
|
||||
|
||||
$this->drupalGet(Url::fromRoute('big_pipe_test_multi_occurrence'));
|
||||
$big_pipe_placeholder_id = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e';
|
||||
$big_pipe_placeholder_id = 'callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=_HAdUpwWmet0TOTe2PSiJuMntExoshbm1kh2wQzzzAA';
|
||||
$expected_placeholder_replacement = '<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="' . $big_pipe_placeholder_id . '">';
|
||||
$this->assertRaw('The count is 1.');
|
||||
$this->assertNoRaw('The count is 2.');
|
||||
|
@ -353,7 +357,7 @@ class BigPipeTest extends WebTestBase {
|
|||
foreach ($expected_big_pipe_placeholders as $big_pipe_placeholder_id => $expected_ajax_response) {
|
||||
$this->pass('BigPipe placeholder: ' . $big_pipe_placeholder_id, 'Debug');
|
||||
// Verify expected placeholder.
|
||||
$expected_placeholder_html = '<div data-big-pipe-placeholder-id="' . $big_pipe_placeholder_id . '"></div>';
|
||||
$expected_placeholder_html = '<span data-big-pipe-placeholder-id="' . $big_pipe_placeholder_id . '"></span>';
|
||||
$this->assertRaw($expected_placeholder_html, 'BigPipe placeholder for placeholder ID "' . $big_pipe_placeholder_id . '" found.');
|
||||
$pos = strpos($this->getRawContent(), $expected_placeholder_html);
|
||||
$placeholder_positions[$pos] = $big_pipe_placeholder_id;
|
||||
|
@ -402,14 +406,18 @@ class BigPipeTest extends WebTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return \Drupal\big_pipe\Tests\BigPipePlaceholderTestCase[]
|
||||
* Ensures CSRF tokens can be generated for the current user's session.
|
||||
*/
|
||||
protected function getTestCases() {
|
||||
// Ensure we can generate CSRF tokens for the current user's session.
|
||||
protected function setCsrfTokenSeedInTestEnvironment() {
|
||||
$session_data = $this->container->get('session_handler.write_safe')->read($this->cookies[$this->getSessionName()]['value']);
|
||||
$csrf_token_seed = unserialize(explode('_sf2_meta|', $session_data)[1])['s'];
|
||||
$this->container->get('session_manager.metadata_bag')->setCsrfTokenSeed($csrf_token_seed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Drupal\big_pipe\Tests\BigPipePlaceholderTestCase[]
|
||||
*/
|
||||
protected function getTestCases($has_session = TRUE) {
|
||||
return BigPipePlaceholderTestCases::cases($this->container, $this->rootUser);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,3 +4,10 @@ big_pipe_regression_test.2678662:
|
|||
_controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::regression2678662'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
big_pipe_regression_test.2802923:
|
||||
path: '/big_pipe_regression_test/2802923'
|
||||
defaults:
|
||||
_controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::regression2802923'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
|
|
@ -17,4 +17,30 @@ class BigPipeRegressionTestController {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testMultipleBodies_2678662()
|
||||
*/
|
||||
public function regression2802923() {
|
||||
return [
|
||||
'#prefix' => BigPipeMarkup::create('<p>Hi, my train will arrive at '),
|
||||
'time' => [
|
||||
'#lazy_builder' => [static::class . '::currentTime', []],
|
||||
'#create_placeholder' => TRUE,
|
||||
],
|
||||
'#suffix' => BigPipeMarkup::create(' — will I still be able to catch the connection to the center?</p>'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* #lazy_builder callback; builds <time> markup with current time.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function currentTime() {
|
||||
return [
|
||||
'#markup' => '<time datetime="' . date('Y-m-d', time()) . '"></time>',
|
||||
'#cache' => ['max-age' => 0]
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,13 +14,19 @@ class BigPipeTestController {
|
|||
* @return array
|
||||
*/
|
||||
public function test() {
|
||||
$has_session = \Drupal::service('session_configuration')->hasSession(\Drupal::requestStack()->getMasterRequest());
|
||||
|
||||
$build = [];
|
||||
|
||||
$cases = BigPipePlaceholderTestCases::cases(\Drupal::getContainer());
|
||||
|
||||
// 1. HTML placeholder: status messages. Drupal renders those automatically,
|
||||
// so all that we need to do in this controller is set a message.
|
||||
drupal_set_message('Hello from BigPipe!');
|
||||
if ($has_session) {
|
||||
// Only set a message if a session already exists, otherwise we always
|
||||
// trigger a session, which means we can't test no-session requests.
|
||||
drupal_set_message('Hello from BigPipe!');
|
||||
}
|
||||
$build['html'] = $cases['html']->renderArray;
|
||||
|
||||
// 2. HTML attribute value placeholder: form action.
|
||||
|
@ -98,7 +104,10 @@ class BigPipeTestController {
|
|||
public static function helloOrYarhar() {
|
||||
return [
|
||||
'#markup' => BigPipeMarkup::create('<marquee>Yarhar llamas forever!</marquee>'),
|
||||
'#cache' => ['max-age' => 0],
|
||||
'#cache' => [
|
||||
'max-age' => 0,
|
||||
'tags' => ['cache_tag_set_in_lazy_builder'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -20,14 +20,14 @@ class BigPipeTestForm extends FormBase {
|
|||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$form['#token'] = FALSE;
|
||||
|
||||
$form['big_pipe'] = array(
|
||||
$form['big_pipe'] = [
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => $this->t('BigPipe works…'),
|
||||
'#options' => [
|
||||
'js' => $this->t('… with JavaScript'),
|
||||
'nojs' => $this->t('… without JavaScript'),
|
||||
],
|
||||
);
|
||||
];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue