updated core from 8.4 to 8.5 : bug with login_destination

This commit is contained in:
Bachir Soussi Chiadmi
2018-03-13 14:01:21 +01:00
parent 0d78da249b
commit e668535a4e
3486 changed files with 89604 additions and 33283 deletions
+3 -2
View File
@@ -245,6 +245,7 @@ class View extends ConfigEntityBase implements ViewEntityInterface {
$display_duplicate = $displays[$old_display_id];
unset($display_duplicate['display_title']);
unset($display_duplicate['display_plugin']);
unset($display_duplicate['new_id']);
$displays[$new_display_id] = NestedArray::mergeDeep($displays[$new_display_id], $display_duplicate);
$displays[$new_display_id]['id'] = $new_display_id;
@@ -387,7 +388,7 @@ class View extends ConfigEntityBase implements ViewEntityInterface {
views_invalidate_cache();
$this->invalidateCaches();
// Rebuild the router if this is a new view, or it's status changed.
// Rebuild the router if this is a new view, or its status changed.
if (!isset($this->original) || ($this->status() != $this->original->status())) {
\Drupal::service('router.builder')->setRebuildNeeded();
}
@@ -456,7 +457,7 @@ class View extends ConfigEntityBase implements ViewEntityInterface {
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
$tempstore = \Drupal::service('user.shared_tempstore')->get('views');
$tempstore = \Drupal::service('tempstore.shared')->get('views');
foreach ($entities as $entity) {
$tempstore->delete($entity->id());
}
+9 -5
View File
@@ -183,6 +183,13 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
'id' => 'entity_operations',
],
];
$data[$revision_table]['operations'] = [
'field' => [
'title' => $this->t('Operations links'),
'help' => $this->t('Provides links to perform entity operations.'),
'id' => 'entity_operations',
],
];
}
if ($this->entityType->hasViewBuilderClass()) {
@@ -378,13 +385,10 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
// @todo Introduce concept of the "main" column for a field, rather than
// assuming the first one is the main column. See also what the
// mapSingleFieldViewsData() method does with $first.
$multiple = (count($field_column_mapping) > 1);
$first = TRUE;
foreach ($field_column_mapping as $field_column_name => $schema_field_name) {
$views_field_name = ($multiple) ? $field_name . '__' . $field_column_name : $field_name;
$table_data[$views_field_name] = $this->mapSingleFieldViewsData($table, $field_name, $field_definition_type, $field_column_name, $field_schema['columns'][$field_column_name]['type'], $first, $field_definition);
$table_data[$views_field_name]['entity field'] = $field_name;
$table_data[$schema_field_name] = $this->mapSingleFieldViewsData($table, $field_name, $field_definition_type, $field_column_name, $field_schema['columns'][$field_column_name]['type'], $first, $field_definition);
$table_data[$schema_field_name]['entity field'] = $field_name;
$first = FALSE;
}
}
@@ -12,6 +12,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the views exposed form.
*
* @internal
*/
class ViewsExposedForm extends FormBase {
@@ -845,4 +845,19 @@ abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
return $form_state_options + $options;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
if ($this->table) {
// Ensure that the view depends on the module that provides the table.
$data = $this->getViewsData()->get($this->table);
if (isset($data['table']['provider'])) {
$dependencies['module'][] = $data['table']['provider'];
}
}
return $dependencies;
}
}
@@ -368,17 +368,17 @@ abstract class PluginBase extends ComponentPluginBase implements ContainerFactor
// We need to validate tokens are valid Twig variables. Twig uses the
// same variable naming rules as PHP.
// @see http://php.net/manual/language.variables.basics.php
assert('preg_match(\'/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/\', $token) === 1', 'Tokens need to be valid Twig variables.');
assert(preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $token) === 1, 'Tokens need to be valid Twig variables.');
$twig_tokens[$token] = $replacement;
}
else {
$parts = explode('.', $token);
$top = array_shift($parts);
assert('preg_match(\'/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/\', $top) === 1', 'Tokens need to be valid Twig variables.');
assert(preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $top) === 1, 'Tokens need to be valid Twig variables.');
$token_array = [array_pop($parts) => $replacement];
foreach (array_reverse($parts) as $key) {
// The key could also be numeric (array index) so allow that.
assert('is_numeric($key) || (preg_match(\'/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/\', $key) === 1)', 'Tokens need to be valid Twig variables.');
assert(is_numeric($key) || preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $key) === 1, 'Tokens need to be valid Twig variables.');
$token_array = [$key => $token_array];
}
if (!isset($twig_tokens[$top])) {
@@ -118,7 +118,7 @@ interface ViewsPluginInterface extends PluginInspectionInterface, DerivativeInsp
/**
* Flattens the structure of form elements.
*
* If a form element has #flatten = TRUE, then all of it's children get moved
* If a form element has #flatten = TRUE, then all of its children get moved
* to the same level as the element itself. So $form['to_be_flattened'][$key]
* becomes $form[$key], and $form['to_be_flattened'] gets unset.
*
@@ -84,6 +84,18 @@ class Page extends PathPluginBase {
);
}
/**
* {@inheritdoc}
*/
protected function getRoute($view_id, $display_id) {
$route = parent::getRoute($view_id, $display_id);
// Explicitly set HTML as the format for Page displays.
$route->setRequirement('_format', 'html');
return $route;
}
/**
* Sets the current page views render array.
*
@@ -0,0 +1,505 @@
<?php
namespace Drupal\views\Plugin\views\field;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\style\Table;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a actions-based bulk operation form element.
*
* @ViewsField("bulk_form")
*/
class BulkForm extends FieldPluginBase implements CacheableDependencyInterface {
use RedirectDestinationTrait;
use UncacheableFieldHandlerTrait;
use EntityTranslationRenderTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The action storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $actionStorage;
/**
* An array of actions that can be executed.
*
* @var \Drupal\system\ActionConfigEntityInterface[]
*/
protected $actions = [];
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new BulkForm object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->actionStorage = $entity_manager->getStorage('action');
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('language_manager')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$entity_type = $this->getEntityType();
// Filter the actions to only include those for this entity type.
$this->actions = array_filter($this->actionStorage->loadMultiple(), function ($action) use ($entity_type) {
return $action->getType() == $entity_type;
});
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
// @todo Consider making the bulk operation form cacheable. See
// https://www.drupal.org/node/2503009.
return 0;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return $this->languageManager->isMultilingual() ? $this->getEntityTranslationRenderer()->getCacheContexts() : [];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return [];
}
/**
* {@inheritdoc}
*/
public function getEntityTypeId() {
return $this->getEntityType();
}
/**
* {@inheritdoc}
*/
protected function getEntityManager() {
return $this->entityManager;
}
/**
* {@inheritdoc}
*/
protected function getLanguageManager() {
return $this->languageManager;
}
/**
* {@inheritdoc}
*/
protected function getView() {
return $this->view;
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['action_title'] = ['default' => $this->t('Action')];
$options['include_exclude'] = [
'default' => 'exclude',
];
$options['selected_actions'] = [
'default' => [],
];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['action_title'] = [
'#type' => 'textfield',
'#title' => $this->t('Action title'),
'#default_value' => $this->options['action_title'],
'#description' => $this->t('The title shown above the actions dropdown.'),
];
$form['include_exclude'] = [
'#type' => 'radios',
'#title' => $this->t('Available actions'),
'#options' => [
'exclude' => $this->t('All actions, except selected'),
'include' => $this->t('Only selected actions'),
],
'#default_value' => $this->options['include_exclude'],
];
$form['selected_actions'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Selected actions'),
'#options' => $this->getBulkOptions(FALSE),
'#default_value' => $this->options['selected_actions'],
];
parent::buildOptionsForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
parent::validateOptionsForm($form, $form_state);
$selected_actions = $form_state->getValue(['options', 'selected_actions']);
$form_state->setValue(['options', 'selected_actions'], array_values(array_filter($selected_actions)));
}
/**
* {@inheritdoc}
*/
public function preRender(&$values) {
parent::preRender($values);
// If the view is using a table style, provide a placeholder for a
// "select all" checkbox.
if (!empty($this->view->style_plugin) && $this->view->style_plugin instanceof Table) {
// Add the tableselect css classes.
$this->options['element_label_class'] .= 'select-all';
// Hide the actual label of the field on the table header.
$this->options['label'] = '';
}
}
/**
* {@inheritdoc}
*/
public function getValue(ResultRow $row, $field = NULL) {
return '<!--form-item-' . $this->options['id'] . '--' . $row->index . '-->';
}
/**
* Form constructor for the bulk form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function viewsForm(&$form, FormStateInterface $form_state) {
// Make sure we do not accidentally cache this form.
// @todo Evaluate this again in https://www.drupal.org/node/2503009.
$form['#cache']['max-age'] = 0;
// Add the tableselect javascript.
$form['#attached']['library'][] = 'core/drupal.tableselect';
$use_revision = array_key_exists('revision', $this->view->getQuery()->getEntityTableInfo());
// Only add the bulk form options and buttons if there are results.
if (!empty($this->view->result)) {
// Render checkboxes for all rows.
$form[$this->options['id']]['#tree'] = TRUE;
foreach ($this->view->result as $row_index => $row) {
$entity = $this->getEntityTranslation($this->getEntity($row), $row);
$form[$this->options['id']][$row_index] = [
'#type' => 'checkbox',
// We are not able to determine a main "title" for each row, so we can
// only output a generic label.
'#title' => $this->t('Update this item'),
'#title_display' => 'invisible',
'#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL,
'#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision),
];
}
// Replace the form submit button label.
$form['actions']['submit']['#value'] = $this->t('Apply to selected items');
// Ensure a consistent container for filters/operations in the view header.
$form['header'] = [
'#type' => 'container',
'#weight' => -100,
];
// Build the bulk operations action widget for the header.
// Allow themes to apply .container-inline on this separate container.
$form['header'][$this->options['id']] = [
'#type' => 'container',
];
$form['header'][$this->options['id']]['action'] = [
'#type' => 'select',
'#title' => $this->options['action_title'],
'#options' => $this->getBulkOptions(),
];
// Duplicate the form actions into the action container in the header.
$form['header'][$this->options['id']]['actions'] = $form['actions'];
}
else {
// Remove the default actions build array.
unset($form['actions']);
}
}
/**
* Returns the available operations for this form.
*
* @param bool $filtered
* (optional) Whether to filter actions to selected actions.
* @return array
* An associative array of operations, suitable for a select element.
*/
protected function getBulkOptions($filtered = TRUE) {
$options = [];
// Filter the action list.
foreach ($this->actions as $id => $action) {
if ($filtered) {
$in_selected = in_array($id, $this->options['selected_actions']);
// If the field is configured to include only the selected actions,
// skip actions that were not selected.
if (($this->options['include_exclude'] == 'include') && !$in_selected) {
continue;
}
// Otherwise, if the field is configured to exclude the selected
// actions, skip actions that were selected.
elseif (($this->options['include_exclude'] == 'exclude') && $in_selected) {
continue;
}
}
$options[$id] = $action->label();
}
return $options;
}
/**
* Submit handler for the bulk form.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* Thrown when the user tried to access an action without access to it.
*/
public function viewsFormSubmit(&$form, FormStateInterface $form_state) {
if ($form_state->get('step') == 'views_form_views_form') {
// Filter only selected checkboxes. Use the actual user input rather than
// the raw form values array, since the site data may change before the
// bulk form is submitted, which can lead to data loss.
$user_input = $form_state->getUserInput();
$selected = array_filter($user_input[$this->options['id']]);
$entities = [];
$action = $this->actions[$form_state->getValue('action')];
$count = 0;
foreach ($selected as $bulk_form_key) {
$entity = $this->loadEntityFromBulkFormKey($bulk_form_key);
// Skip execution if the user did not have access.
if (!$action->getPlugin()->access($entity, $this->view->getUser())) {
$this->drupalSetMessage($this->t('No access to execute %action on the @entity_type_label %entity_label.', [
'%action' => $action->label(),
'@entity_type_label' => $entity->getEntityType()->getLabel(),
'%entity_label' => $entity->label()
]), 'error');
continue;
}
$count++;
$entities[$bulk_form_key] = $entity;
}
$action->execute($entities);
$operation_definition = $action->getPluginDefinition();
if (!empty($operation_definition['confirm_form_route_name'])) {
$options = [
'query' => $this->getDestinationArray(),
];
$form_state->setRedirect($operation_definition['confirm_form_route_name'], [], $options);
}
else {
// Don't display the message unless there are some elements affected and
// there is no confirmation form.
if ($count) {
drupal_set_message($this->formatPlural($count, '%action was applied to @count item.', '%action was applied to @count items.', [
'%action' => $action->label(),
]));
}
}
}
}
/**
* Returns the message to be displayed when there are no selected items.
*
* @return string
* Message displayed when no items are selected.
*/
protected function emptySelectedMessage() {
return $this->t('No items selected.');
}
/**
* {@inheritdoc}
*/
public function viewsFormValidate(&$form, FormStateInterface $form_state) {
$selected = array_filter($form_state->getValue($this->options['id']));
if (empty($selected)) {
$form_state->setErrorByName('', $this->emptySelectedMessage());
}
}
/**
* {@inheritdoc}
*/
public function query() {
if ($this->languageManager->isMultilingual()) {
$this->getEntityTranslationRenderer()->query($this->query, $this->relationship);
}
}
/**
* {@inheritdoc}
*/
public function clickSortable() {
return FALSE;
}
/**
* Wraps drupal_set_message().
*/
protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
drupal_set_message($message, $type, $repeat);
}
/**
* Calculates a bulk form key.
*
* This generates a key that is used as the checkbox return value when
* submitting a bulk form. This key allows the entity for the row to be loaded
* totally independently of the executed view row.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to calculate a bulk form key for.
* @param bool $use_revision
* Whether the revision id should be added to the bulk form key. This should
* be set to TRUE only if the view is listing entity revisions.
*
* @return string
* The bulk form key representing the entity's id, language and revision (if
* applicable) as one string.
*
* @see self::loadEntityFromBulkFormKey()
*/
protected function calculateEntityBulkFormKey(EntityInterface $entity, $use_revision) {
$key_parts = [$entity->language()->getId(), $entity->id()];
if ($entity instanceof RevisionableInterface && $use_revision) {
$key_parts[] = $entity->getRevisionId();
}
// An entity ID could be an arbitrary string (although they are typically
// numeric). JSON then Base64 encoding ensures the bulk_form_key is
// safe to use in HTML, and that the key parts can be retrieved.
$key = json_encode($key_parts);
return base64_encode($key);
}
/**
* Loads an entity based on a bulk form key.
*
* @param string $bulk_form_key
* The bulk form key representing the entity's id, language and revision (if
* applicable) as one string.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity loaded in the state (language, optionally revision) specified
* as part of the bulk form key.
*/
protected function loadEntityFromBulkFormKey($bulk_form_key) {
$key = base64_decode($bulk_form_key);
$key_parts = json_decode($key);
$revision_id = NULL;
// If there are 3 items, vid will be last.
if (count($key_parts) === 3) {
$revision_id = array_pop($key_parts);
}
// The first two items will always be langcode and ID.
$id = array_pop($key_parts);
$langcode = array_pop($key_parts);
// Load the entity or a specific revision depending on the given key.
$storage = $this->entityManager->getStorage($this->getEntityType());
$entity = $revision_id ? $storage->loadRevision($revision_id) : $storage->load($id);
if ($entity instanceof TranslatableInterface) {
$entity = $entity->getTranslation($langcode);
}
return $entity;
}
}
@@ -2,6 +2,7 @@
namespace Drupal\views\Plugin\views\field;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ResultRow;
/**
@@ -20,12 +21,22 @@ class EntityLink extends LinkBase {
return $this->getEntity($row) ? parent::render($row) : [];
}
/**
* {@inheritdoc}
*/
protected function renderLink(ResultRow $row) {
if ($this->options['output_url_as_text']) {
return $this->getUrlInfo($row)->toString();
}
return parent::renderLink($row);
}
/**
* {@inheritdoc}
*/
protected function getUrlInfo(ResultRow $row) {
$template = $this->getEntityLinkTemplate();
return $this->getEntity($row)->urlInfo($template);
return $this->getEntity($row)->toUrl($template)->setAbsolute($this->options['absolute']);
}
/**
@@ -45,4 +56,34 @@ class EntityLink extends LinkBase {
return $this->t('view');
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['output_url_as_text'] = ['default' => FALSE];
$options['absolute'] = ['default' => FALSE];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['output_url_as_text'] = [
'#type' => 'checkbox',
'#title' => $this->t('Output the URL as text'),
'#default_value' => $this->options['output_url_as_text'],
];
$form['absolute'] = [
'#type' => 'checkbox',
'#title' => $this->t('Use absolute link (begins with "http://")'),
'#default_value' => $this->options['absolute'],
'#description' => $this->t('Enable this option to output an absolute link. Required if you want to use the path as a link destination.'),
];
parent::buildOptionsForm($form, $form_state);
// Only show the 'text' field if we don't want to output the raw URL.
$form['text']['#states']['visible'][':input[name="options[output_url_as_text]"]'] = ['checked' => FALSE];
}
}
@@ -84,7 +84,7 @@ class EntityOperations extends FieldPluginBase {
$options = parent::defineOptions();
$options['destination'] = [
'default' => TRUE,
'default' => FALSE,
];
return $options;
@@ -99,7 +99,7 @@ class EntityOperations extends FieldPluginBase {
$form['destination'] = [
'#type' => 'checkbox',
'#title' => $this->t('Include destination'),
'#description' => $this->t('Include a <code>destination</code> parameter in the link to return the user to the original view upon completing the link action.'),
'#description' => $this->t('Enforce a <code>destination</code> parameter in the link to return the user to the original view upon completing the link action. Most operations include a destination by default and this setting is no longer needed.'),
'#default_value' => $this->options['destination'],
];
}
@@ -1056,7 +1056,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
'#type' => 'textarea',
'#title' => $this->t('No results text'),
'#default_value' => $this->options['empty'],
'#description' => $this->t('Provide text to display if this field contains an empty result. You may include HTML. You may enter data from this view as per the "Replacement patterns" in the "Rewrite Results" section below.'),
'#description' => $this->t('Provide text to display if this field contains an empty result. You may include HTML. You may enter data from this view as per the "Replacement patterns" in the "Rewrite Results" section above.'),
'#fieldset' => 'empty_field_behavior',
];
@@ -229,7 +229,9 @@ class InOperator extends FilterPluginBase {
'#default_value' => $default_value,
// These are only valid for 'select' type, but do no harm to checkboxes.
'#multiple' => TRUE,
'#size' => count($options) > 8 ? 8 : count($options),
// The value options can be a multidimensional array if the value form
// type is a select list, so make sure that they are counted correctly.
'#size' => min(count($options, COUNT_RECURSIVE), 8),
];
$user_input = $form_state->getUserInput();
if ($exposed && !isset($user_input[$identifier])) {
@@ -26,9 +26,69 @@ class NumericFilter extends FilterPluginBase {
],
];
$options['expose']['contains']['placeholder'] = ['default' => ''];
$options['expose']['contains']['min_placeholder'] = ['default' => ''];
$options['expose']['contains']['max_placeholder'] = ['default' => ''];
return $options;
}
/**
* {@inheritdoc}
*/
public function defaultExposeOptions() {
parent::defaultExposeOptions();
$this->options['expose']['min_placeholder'] = NULL;
$this->options['expose']['max_placeholder'] = NULL;
$this->options['expose']['placeholder'] = NULL;
}
/**
* {@inheritdoc}
*/
public function buildExposeForm(&$form, FormStateInterface $form_state) {
parent::buildExposeForm($form, $form_state);
$form['expose']['min_placeholder'] = [
'#type' => 'textfield',
'#default_value' => $this->options['expose']['min_placeholder'],
'#title' => $this->t('Min placeholder'),
'#size' => 40,
'#description' => $this->t('Hint text that appears inside the Min field when empty.'),
];
$form['expose']['max_placeholder'] = [
'#type' => 'textfield',
'#default_value' => $this->options['expose']['max_placeholder'],
'#title' => $this->t('Max placeholder'),
'#size' => 40,
'#description' => $this->t('Hint text that appears inside the Max field when empty.'),
];
// Setup #states for all operators with two value.
$states = [[':input[name="options[expose][use_operator]"]' => ['checked' => TRUE]]];
foreach ($this->operatorValues(2) as $operator) {
$states[] = [
':input[name="options[operator]"]' => ['value' => $operator],
];
}
$form['expose']['min_placeholder']['#states']['visible'] = $states;
$form['expose']['max_placeholder']['#states']['visible'] = $states;
$form['expose']['placeholder'] = [
'#type' => 'textfield',
'#default_value' => $this->options['expose']['placeholder'],
'#title' => $this->t('Placeholder'),
'#size' => 40,
'#description' => $this->t('Hint text that appears inside the field when empty.'),
];
// Setup #states for all operators with one value.
$form['expose']['placeholder']['#states']['visible'] = [[':input[name="options[expose][use_operator]"]' => ['checked' => TRUE]]];
foreach ($this->operatorValues(1) as $operator) {
$form['expose']['placeholder']['#states']['visible'][] = [
':input[name="options[operator]"]' => ['value' => $operator],
];
}
}
public function operators() {
$operators = [
'<' => [
@@ -165,6 +225,9 @@ class NumericFilter extends FilterPluginBase {
'#size' => 30,
'#default_value' => $this->value['value'],
];
if (!empty($this->options['expose']['placeholder'])) {
$form['value']['value']['#attributes']['placeholder'] = $this->options['expose']['placeholder'];
}
// Setup #states for all operators with one value.
foreach ($this->operatorValues(1) as $operator) {
$form['value']['value']['#states']['visible'][] = [
@@ -185,6 +248,9 @@ class NumericFilter extends FilterPluginBase {
'#size' => 30,
'#default_value' => $this->value['value'],
];
if (!empty($this->options['expose']['placeholder'])) {
$form['value']['#attributes']['placeholder'] = $this->options['expose']['placeholder'];
}
if ($exposed && !isset($user_input[$identifier])) {
$user_input[$identifier] = $this->value['value'];
$form_state->setUserInput($user_input);
@@ -197,14 +263,20 @@ class NumericFilter extends FilterPluginBase {
'#title' => !$exposed ? $this->t('Min') : $this->exposedInfo()['label'],
'#size' => 30,
'#default_value' => $this->value['min'],
'#description' => !$exposed ? '' : $this->exposedInfo()['description']
'#description' => !$exposed ? '' : $this->exposedInfo()['description'],
];
if (!empty($this->options['expose']['min_placeholder'])) {
$form['value']['min']['#attributes']['placeholder'] = $this->options['expose']['min_placeholder'];
}
$form['value']['max'] = [
'#type' => 'textfield',
'#title' => !$exposed ? $this->t('And max') : $this->t('And'),
'#size' => 30,
'#default_value' => $this->value['max'],
];
if (!empty($this->options['expose']['max_placeholder'])) {
$form['value']['max']['#attributes']['placeholder'] = $this->options['expose']['max_placeholder'];
}
if ($which == 'all') {
$states = [];
// Setup #states for all operators with two values.
@@ -27,10 +27,33 @@ class StringFilter extends FilterPluginBase {
$options = parent::defineOptions();
$options['expose']['contains']['required'] = ['default' => FALSE];
$options['expose']['contains']['placeholder'] = ['default' => ''];
return $options;
}
/**
* {@inheritdoc}
*/
public function defaultExposeOptions() {
parent::defaultExposeOptions();
$this->options['expose']['placeholder'] = NULL;
}
/**
* {@inheritdoc}
*/
public function buildExposeForm(&$form, FormStateInterface $form_state) {
parent::buildExposeForm($form, $form_state);
$form['expose']['placeholder'] = [
'#type' => 'textfield',
'#default_value' => $this->options['expose']['placeholder'],
'#title' => $this->t('Placeholder'),
'#size' => 40,
'#description' => $this->t('Hint text that appears inside the field when empty.'),
];
}
/**
* This kind of construct makes it relatively easy for a child class
* to add or remove functionality by overriding this function and
@@ -211,6 +234,9 @@ class StringFilter extends FilterPluginBase {
'#size' => 30,
'#default_value' => $this->value,
];
if (!empty($this->options['expose']['placeholder'])) {
$form['value']['#attributes']['placeholder'] = $this->options['expose']['placeholder'];
}
$user_input = $form_state->getUserInput();
if ($exposed && !isset($user_input[$identifier])) {
$user_input[$identifier] = $this->value;
@@ -0,0 +1,62 @@
<?php
namespace Drupal\views\Plugin\views\query;
/**
* Defines an interface for handling date queries with SQL.
*
* @internal
* Classes implementing this interface should only be used by the Views SQL
* query plugin.
*
* @see \Drupal\views\Plugin\views\query\Sql
*/
interface DateSqlInterface {
/**
* Returns a native database expression for a given field.
*
* @param string $field
* The query field that will be used in the expression.
* @param bool $string_date
* For certain databases, date format functions vary depending on string or
* numeric storage.
*
* @return string
* An expression representing a date field with timezone.
*/
public function getDateField($field, $string_date);
/**
* Creates a native database date formatting.
*
* @param string $field
* An appropriate query expression pointing to the date field.
* @param string $format
* A format string for the result. For example: 'Y-m-d H:i:s'.
*
* @return string
* A string representing the field formatted as a date as specified by
* $format.
*/
public function getDateFormat($field, $format);
/**
* Applies the given offset to the given field.
*
* @param string &$field
* The date field in a string format.
* @param int $offset
* The timezone offset in seconds.
*/
public function setFieldTimezoneOffset(&$field, $offset);
/**
* Set the database to the given timezone.
*
* @param string $offset
* The timezone.
*/
public function setTimezoneOffset($offset);
}
@@ -0,0 +1,93 @@
<?php
namespace Drupal\views\Plugin\views\query;
use Drupal\Core\Database\Connection;
/**
* MySQL-specific date handling.
*
* @internal
* This class should only be used by the Views SQL query plugin.
*
* @see \Drupal\views\Plugin\views\query\Sql
*/
class MysqlDateSql implements DateSqlInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* An array of PHP-to-MySQL replacement patterns.
*/
protected static $replace = [
'Y' => '%Y',
'y' => '%y',
'M' => '%b',
'm' => '%m',
'n' => '%c',
'F' => '%M',
'D' => '%a',
'd' => '%d',
'l' => '%W',
'j' => '%e',
'W' => '%v',
'H' => '%H',
'h' => '%h',
'i' => '%i',
's' => '%s',
'A' => '%p',
];
/**
* Constructs the MySQL-specific date sql class.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
*/
public function __construct(Connection $database) {
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public function getDateField($field, $string_date) {
if ($string_date) {
return $field;
}
// Base date field storage is timestamp, so the date to be returned here is
// epoch + stored value (seconds from epoch).
return "DATE_ADD('19700101', INTERVAL $field SECOND)";
}
/**
* {@inheritdoc}
*/
public function getDateFormat($field, $format) {
$format = strtr($format, static::$replace);
return "DATE_FORMAT($field, '$format')";
}
/**
* {@inheritdoc}
*/
public function setTimezoneOffset($offset) {
$this->database->query("SET @@session.time_zone = '$offset'");
}
/**
* {@inheritdoc}
*/
public function setFieldTimezoneOffset(&$field, $offset) {
if (!empty($offset)) {
$field = "($field + INTERVAL $offset SECOND)";
}
}
}
@@ -0,0 +1,93 @@
<?php
namespace Drupal\views\Plugin\views\query;
use Drupal\Core\Database\Connection;
/**
* PostgreSQL-specific date handling.
*
* @internal
* This class should only be used by the Views SQL query plugin.
*
* @see \Drupal\views\Plugin\views\query\Sql
*/
class PostgresqlDateSql implements DateSqlInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* An array of PHP-to-PostgreSQL replacement patterns.
*
* @var array
*/
protected static $replace = [
'Y' => 'YYYY',
'y' => 'YY',
'M' => 'Mon',
'm' => 'MM',
// No format for Numeric representation of a month, without leading zeros.
'n' => 'MM',
'F' => 'Month',
'D' => 'Dy',
'd' => 'DD',
'l' => 'Day',
// No format for Day of the month without leading zeros.
'j' => 'DD',
'W' => 'IW',
'H' => 'HH24',
'h' => 'HH12',
'i' => 'MI',
's' => 'SS',
'A' => 'AM',
];
/**
* Constructs the PostgreSQL-specific date sql class.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
*/
public function __construct(Connection $database) {
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public function getDateField($field, $string_date) {
if ($string_date) {
// Ensures compatibility with field offset operation below.
return "TO_TIMESTAMP($field, 'YYYY-MM-DD HH24:MI:SS')";
}
return "TO_TIMESTAMP($field)";
}
/**
* {@inheritdoc}
*/
public function getDateFormat($field, $format) {
$format = strtr($format, static::$replace);
return "TO_CHAR($field, '$format')";
}
/**
* {@inheritdoc}
*/
public function setFieldTimezoneOffset(&$field, $offset) {
$field = "($field + INTERVAL '$offset SECONDS')";
}
/**
* {@inheritdoc}
*/
public function setTimezoneOffset($offset) {
$this->database->query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
}
}
@@ -206,11 +206,17 @@ abstract class QueryPluginBase extends PluginBase implements CacheableDependency
*
* @param string $field
* The query field that will be used in the expression.
* @param bool $string_date
* For certain databases, date format functions vary depending on string or
* numeric storage.
* @param bool $calculate_offset
* If set to TRUE, the timezone offset will be included in the returned
* field.
*
* @return string
* An expression representing a timestamp with time zone.
*/
public function getDateField($field) {
public function getDateField($field, $string_date = FALSE, $calculate_offset = TRUE) {
return $field;
}
@@ -346,6 +352,36 @@ abstract class QueryPluginBase extends PluginBase implements CacheableDependency
return [];
}
/**
* Applies a timezone offset to the given field.
*
* @param string &$field
* The date field, in string format.
* @param int $offset
* The timezone offset to apply to the field.
*/
public function setFieldTimezoneOffset(&$field, $offset) {
// No-op. Timezone offsets are implementation-specific and should implement
// this method as needed.
}
/**
* Get the timezone offset in seconds.
*
* @return int
* The offset, in seconds, for the timezone being used.
*/
public function getTimezoneOffset() {
$timezone = $this->setupTimezone();
$offset = 0;
if ($timezone) {
$dtz = new \DateTimeZone($timezone);
$dt = new \DateTime('now', $dtz);
$offset = $dtz->getOffset($dt);
}
return $offset;
}
}
/**
+52 -155
View File
@@ -123,6 +123,13 @@ class Sql extends QueryPluginBase {
*/
protected $entityTypeManager;
/**
* The database-specific date handler.
*
* @var \Drupal\views\Plugin\views\query\DateSqlInterface
*/
protected $dateSql;
/**
* Constructs a Sql object.
*
@@ -134,11 +141,14 @@ class Sql extends QueryPluginBase {
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\views\Plugin\views\query\DateSqlInterface $date_sql
* The database-specific date handler.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, DateSqlInterface $date_sql) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
$this->dateSql = $date_sql;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
@@ -146,7 +156,8 @@ class Sql extends QueryPluginBase {
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager')
$container->get('entity_type.manager'),
$container->get('views.date_sql')
);
}
@@ -187,6 +198,27 @@ class Sql extends QueryPluginBase {
];
}
/**
* Returns a reference to the table queue array for this query.
*
* Because this method returns by reference, alter hooks may edit the tables
* array directly to make their changes. If just adding tables, however, the
* use of the addTable() method is preferred.
*
* Note that if you want to manipulate the table queue array, this method must
* be called by reference as well:
*
* @code
* $tables =& $query->getTableQueue();
* @endcode
*
* @return array
* A reference to the table queue array structure.
*/
public function &getTableQueue() {
return $this->tableQueue;
}
/**
* Set the view to be distinct (per base field).
*
@@ -1762,175 +1794,40 @@ class Sql extends QueryPluginBase {
/**
* {@inheritdoc}
*/
public function getDateField($field) {
$db_type = Database::getConnection()->databaseType();
$offset = $this->setupTimezone();
if (isset($offset) && !is_numeric($offset)) {
$dtz = new \DateTimeZone($offset);
$dt = new \DateTime('now', $dtz);
$offset_seconds = $dtz->getOffset($dt);
public function getDateField($field, $string_date = FALSE, $calculate_offset = TRUE) {
$field = $this->dateSql->getDateField($field, $string_date);
if ($calculate_offset && $offset = $this->getTimezoneOffset()) {
$this->setFieldTimezoneOffset($field, $offset);
}
switch ($db_type) {
case 'mysql':
$field = "DATE_ADD('19700101', INTERVAL $field SECOND)";
if (!empty($offset)) {
$field = "($field + INTERVAL $offset_seconds SECOND)";
}
break;
case 'pgsql':
$field = "TO_TIMESTAMP($field)";
if (!empty($offset)) {
$field = "($field + INTERVAL '$offset_seconds SECONDS')";
}
break;
case 'sqlite':
if (!empty($offset)) {
$field = "($field + $offset_seconds)";
}
break;
}
return $field;
}
/**
* {@inheritdoc}
*/
public function setFieldTimezoneOffset(&$field, $offset) {
$this->dateSql->setFieldTimezoneOffset($field, $offset);
}
/**
* {@inheritdoc}
*/
public function setupTimezone() {
$timezone = drupal_get_user_timezone();
// set up the database timezone
$db_type = Database::getConnection()->databaseType();
if (in_array($db_type, ['mysql', 'pgsql'])) {
$offset = '+00:00';
static $already_set = FALSE;
if (!$already_set) {
if ($db_type == 'pgsql') {
Database::getConnection()->query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
}
elseif ($db_type == 'mysql') {
Database::getConnection()->query("SET @@session.time_zone = '$offset'");
}
$already_set = TRUE;
}
// Set the database timezone offset.
static $already_set = FALSE;
if (!$already_set) {
$this->dateSql->setTimezoneOffset('+00:00');
$already_set = TRUE;
}
return $timezone;
return parent::setupTimezone();
}
/**
* {@inheritdoc}
*/
public function getDateFormat($field, $format, $string_date = FALSE) {
$db_type = Database::getConnection()->databaseType();
switch ($db_type) {
case 'mysql':
$replace = [
'Y' => '%Y',
'y' => '%y',
'M' => '%b',
'm' => '%m',
'n' => '%c',
'F' => '%M',
'D' => '%a',
'd' => '%d',
'l' => '%W',
'j' => '%e',
'W' => '%v',
'H' => '%H',
'h' => '%h',
'i' => '%i',
's' => '%s',
'A' => '%p',
];
$format = strtr($format, $replace);
return "DATE_FORMAT($field, '$format')";
case 'pgsql':
$replace = [
'Y' => 'YYYY',
'y' => 'YY',
'M' => 'Mon',
'm' => 'MM',
// No format for Numeric representation of a month, without leading
// zeros.
'n' => 'MM',
'F' => 'Month',
'D' => 'Dy',
'd' => 'DD',
'l' => 'Day',
// No format for Day of the month without leading zeros.
'j' => 'DD',
'W' => 'IW',
'H' => 'HH24',
'h' => 'HH12',
'i' => 'MI',
's' => 'SS',
'A' => 'AM',
];
$format = strtr($format, $replace);
if (!$string_date) {
return "TO_CHAR($field, '$format')";
}
// In order to allow for partials (eg, only the year), transform to a
// date, back to a string again.
return "TO_CHAR(TO_TIMESTAMP($field, 'YYYY-MM-DD HH24:MI:SS'), '$format')";
case 'sqlite':
$replace = [
'Y' => '%Y',
// No format for 2 digit year number.
'y' => '%Y',
// No format for 3 letter month name.
'M' => '%m',
'm' => '%m',
// No format for month number without leading zeros.
'n' => '%m',
// No format for full month name.
'F' => '%m',
// No format for 3 letter day name.
'D' => '%d',
'd' => '%d',
// No format for full day name.
'l' => '%d',
// no format for day of month number without leading zeros.
'j' => '%d',
'W' => '%W',
'H' => '%H',
// No format for 12 hour hour with leading zeros.
'h' => '%H',
'i' => '%M',
's' => '%S',
// No format for AM/PM.
'A' => '',
];
$format = strtr($format, $replace);
// Don't use the 'unixepoch' flag for string date comparisons.
$unixepoch = $string_date ? '' : ", 'unixepoch'";
// SQLite does not have a ISO week substitution string, so it needs
// special handling.
// @see http://wikipedia.org/wiki/ISO_week_date#Calculation
// @see http://stackoverflow.com/a/15511864/1499564
if ($format === '%W') {
$expression = "((strftime('%j', date(strftime('%Y-%m-%d', $field" . $unixepoch . "), '-3 days', 'weekday 4')) - 1) / 7 + 1)";
}
else {
$expression = "strftime('$format', $field" . $unixepoch . ")";
}
// The expression yields a string, but the comparison value is an
// integer in case the comparison value is a float, integer, or numeric.
// All of the above SQLite format tokens only produce integers. However,
// the given $format may contain 'Y-m-d', which results in a string.
// @see \Drupal\Core\Database\Driver\sqlite\Connection::expandArguments()
// @see http://www.sqlite.org/lang_datefunc.html
// @see http://www.sqlite.org/lang_expr.html#castexpr
if (preg_match('/^(?:%\w)+$/', $format)) {
$expression = "CAST($expression AS NUMERIC)";
}
return $expression;
}
return $this->dateSql->getDateFormat($field, $format);
}
}
@@ -0,0 +1,122 @@
<?php
namespace Drupal\views\Plugin\views\query;
use Drupal\Core\Database\Connection;
/**
* SQLite-specific date handling.
*
* @internal
* This class should only be used by the Views SQL query plugin.
*
* @see \Drupal\views\Plugin\views\query\Sql
*/
class SqliteDateSql implements DateSqlInterface {
/**
* The database connection.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* An array of PHP-to-SQLite date replacement patterns.
*
* @var array
*/
protected static $replace = [
'Y' => '%Y',
// No format for 2 digit year number.
'y' => '%Y',
// No format for 3 letter month name.
'M' => '%m',
'm' => '%m',
// No format for month number without leading zeros.
'n' => '%m',
// No format for full month name.
'F' => '%m',
// No format for 3 letter day name.
'D' => '%d',
'd' => '%d',
// No format for full day name.
'l' => '%d',
// no format for day of month number without leading zeros.
'j' => '%d',
'W' => '%W',
'H' => '%H',
// No format for 12 hour hour with leading zeros.
'h' => '%H',
'i' => '%M',
's' => '%S',
// No format for AM/PM.
'A' => '',
];
/**
* Constructs the SQLite-specific date sql class.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection.
*/
public function __construct(Connection $database) {
$this->database = $database;
}
/**
* {@inheritdoc}
*/
public function getDateField($field, $string_date) {
if ($string_date) {
$field = "strftime('%s', $field)";
}
return $field;
}
/**
* {@inheritdoc}
*/
public function getDateFormat($field, $format) {
$format = strtr($format, static::$replace);
// SQLite does not have a ISO week substitution string, so it needs special
// handling.
// @see http://wikipedia.org/wiki/ISO_week_date#Calculation
// @see http://stackoverflow.com/a/15511864/1499564
if ($format === '%W') {
$expression = "((strftime('%j', date(strftime('%Y-%m-%d', $field, 'unixepoch'), '-3 days', 'weekday 4')) - 1) / 7 + 1)";
}
else {
$expression = "strftime('$format', $field, 'unixepoch')";
}
// The expression yields a string, but the comparison value is an integer in
// case the comparison value is a float, integer, or numeric. All of the
// above SQLite format tokens only produce integers. However, the given
// $format may contain 'Y-m-d', which results in a string.
// @see \Drupal\Core\Database\Driver\sqlite\Connection::expandArguments()
// @see http://www.sqlite.org/lang_datefunc.html
// @see http://www.sqlite.org/lang_expr.html#castexpr
if (preg_match('/^(?:%\w)+$/', $format)) {
$expression = "CAST($expression AS NUMERIC)";
}
return $expression;
}
/**
* {@inheritdoc}
*/
public function setTimezoneOffset($offset) {
// Nothing to do here.
}
/**
* {@inheritdoc}
*/
public function setFieldTimezoneOffset(&$field, $offset, $string_date = FALSE) {
if (!empty($offset)) {
$field = "($field + $offset)";
}
}
}
@@ -169,11 +169,11 @@ abstract class RelationshipPluginBase extends HandlerBase {
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
// Add the provider of the relationship's base table to the dependencies.
$table_data = $this->getViewsData()->get($this->definition['base']);
return [
'module' => [$table_data['table']['provider']],
];
$dependencies['module'][] = $table_data['table']['provider'];
return $dependencies;
}
}
@@ -23,7 +23,7 @@ use Drupal\Core\Form\FormStateInterface;
class Fields extends RowPluginBase {
/**
* Does the row plugin support to add fields to it's output.
* Does the row plugin support to add fields to its output.
*
* @var bool
*/
@@ -18,7 +18,7 @@ use Drupal\Core\Form\FormStateInterface;
class OpmlFields extends RowPluginBase {
/**
* Does the row plugin support to add fields to it's output.
* Does the row plugin support to add fields to its output.
*
* @var bool
*/
@@ -43,7 +43,7 @@ abstract class RowPluginBase extends PluginBase {
protected $usesOptions = TRUE;
/**
* Does the row plugin support to add fields to it's output.
* Does the row plugin support to add fields to its output.
*
* @var bool
*/
@@ -19,7 +19,7 @@ use Drupal\Core\Url;
class RssFields extends RowPluginBase {
/**
* Does the row plugin support to add fields to it's output.
* Does the row plugin support to add fields to its output.
*
* @var bool
*/
@@ -71,7 +71,7 @@ abstract class StylePluginBase extends PluginBase {
protected $usesGrouping = TRUE;
/**
* Does the style plugin for itself support to add fields to it's output.
* Does the style plugin for itself support to add fields to its output.
*
* This option only makes sense on style plugins without row plugins, like
* for example table.
@@ -3,6 +3,7 @@
namespace Drupal\views\Plugin\views\style;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\wizard\WizardInterface;
@@ -23,7 +24,7 @@ use Drupal\views\Plugin\views\wizard\WizardInterface;
class Table extends StylePluginBase implements CacheableDependencyInterface {
/**
* Does the style plugin for itself support to add fields to it's output.
* Does the style plugin for itself support to add fields to its output.
*
* @var bool
*/
@@ -423,7 +424,7 @@ class Table extends StylePluginBase implements CacheableDependencyInterface {
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return 0;
return Cache::PERMANENT;
}
/**
@@ -3,6 +3,7 @@
namespace Drupal\views\Plugin\views\wizard;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\UrlGeneratorTrait;
@@ -140,7 +141,7 @@ abstract class WizardPluginBase extends PluginBase implements WizardInterface {
$entity_types = \Drupal::entityManager()->getDefinitions();
foreach ($entity_types as $entity_type_id => $entity_type) {
if ($this->base_table == $entity_type->getBaseTable() || $this->base_table == $entity_type->getDataTable()) {
if (in_array($this->base_table, [$entity_type->getBaseTable(), $entity_type->getDataTable(), $entity_type->getRevisionTable(), $entity_type->getRevisionDataTable()], TRUE)) {
$this->entityType = $entity_type;
$this->entityTypeId = $entity_type_id;
}
@@ -165,6 +166,21 @@ abstract class WizardPluginBase extends PluginBase implements WizardInterface {
public function getFilters() {
$filters = [];
// Add a default filter on the publishing status field, if available.
if ($this->entityType && is_subclass_of($this->entityType->getClass(), EntityPublishedInterface::class)) {
$field_name = $this->entityType->getKey('published');
$this->filters = [
$field_name => [
'value' => TRUE,
'table' => $this->base_table,
'field' => $field_name,
'plugin_id' => 'boolean',
'entity_type' => $this->entityTypeId,
'entity_field' => $field_name,
]
] + $this->filters;
}
$default = $this->filter_defaults;
foreach ($this->filters as $name => $info) {
@@ -35,8 +35,8 @@ class FieldApiDataTest extends FieldTestBase {
*/
protected $translationNodes;
protected function setUp() {
parent::setUp(FALSE);
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$field_names = $this->setUpFieldStorages(4);
@@ -26,8 +26,8 @@ class DisplayFeedTest extends PluginTestBase {
*/
public static $modules = ['block', 'node', 'views'];
protected function setUp() {
parent::setUp();
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->enableViewsTestModule();
@@ -27,8 +27,8 @@ class StyleOpmlTest extends PluginTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->enableViewsTestModule();
@@ -0,0 +1,41 @@
<?php
namespace Drupal\views\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
use Drupal\views\Entity\View;
/**
* Tests that the additional settings are added to the entity link field.
*
* @see views_post_update_entity_link_url()
*
* @group Update
*/
class EntityLinkOutputUrlUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../tests/fixtures/update/entity-link-output-url.php',
];
}
/**
* Tests that the additional settings are added to the config.
*/
public function testViewsPostUpdateEntityLinkUrl() {
$this->runUpdates();
// Load and initialize our test view.
$view = View::load('node_link_update_test');
$data = $view->toArray();
// Check that the field contains the new values.
$this->assertIdentical(FALSE, $data['display']['default']['display_options']['fields']['view_node']['output_url_as_text']);
$this->assertIdentical(FALSE, $data['display']['default']['display_options']['fields']['view_node']['absolute']);
}
}
@@ -0,0 +1,54 @@
<?php
namespace Drupal\views\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
use Drupal\views\Views;
/**
* Tests the upgrade path for views multi-value base field data.
*
* @see views_update_8500()
*
* @group Update
*/
class EntityViewsMultiValueBaseFieldDataUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.views-entity-views-data-2846614.php',
];
}
/**
* Tests multi-value base field views data is updated correctly.
*/
public function testUpdateMultiValueBaseFields() {
$this->runUpdates();
$view = Views::getView('test_user_multi_value');
$display = $view->storage->get('display');
// Check each handler type present in the configuration to make sure the
// field got updated correctly.
foreach (['fields', 'filters', 'arguments'] as $type) {
$handler_config = $display['default']['display_options'][$type]['roles'];
// The ID should remain unchanged. Otherwise the update handler could
// overwrite a separate handler config.
$this->assertEqual('roles', $handler_config['id']);
// The field should be updated from 'roles' to the correct column name.
$this->assertEqual('roles_target_id', $handler_config['field']);
// Check the table is still correct.
$this->assertEqual('user__roles', $handler_config['table']);
// The plugin ID should be updated as well.
$this->assertEqual($type === 'arguments' ? 'user__roles_rid' : 'user_roles', $handler_config['plugin_id']);
}
}
}
@@ -19,8 +19,8 @@ class ViewAjaxTest extends ViewTestBase {
*/
public static $testViews = ['test_ajax_view', 'test_view'];
protected function setUp() {
parent::setUp();
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
$this->enableViewsTestModule();
}
@@ -21,8 +21,8 @@ abstract class WizardTestBase extends ViewTestBase {
*/
public static $modules = ['node', 'views_ui', 'block', 'rest'];
protected function setUp() {
parent::setUp();
protected function setUp($import_test_views = TRUE) {
parent::setUp($import_test_views);
// Create and log in a user with administer views permission.
$views_admin = $this->drupalCreateUser(['administer views', 'administer blocks', 'bypass node access', 'access user profiles', 'view all revisions']);
+1 -1
View File
@@ -331,7 +331,7 @@ class ViewExecutable {
protected $request;
/**
* Does this view already have loaded it's handlers.
* Does this view already have loaded its handlers.
*
* @todo Group with other static properties.
*