Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

View file

@ -77,4 +77,3 @@ views.area.http_status_code:
status_code:
type: integer
label: 'HTTP status code'

View file

@ -820,3 +820,20 @@ views_cache:
views_display_extender:
type: mapping
label: 'Display extender settings'
views_field_bulk_form:
type: views_field
label: 'Bulk operation'
mapping:
action_title:
type: label
label: 'Action title'
include_exclude:
type: string
label: 'Available actions'
selected_actions:
type: sequence
label: 'Available actions'
sequence:
type: string
label: 'Action'

View file

@ -49,9 +49,6 @@ views.display.page:
context:
type: string
label: 'Context'
expanded:
type: boolean
label: 'Expanded'
tab_options:
type: mapping
label: 'Tab options'

View file

@ -1,8 +1,8 @@
# Schema for the views entity reference selection plugins.
# Schema for the entity reference 'views' selection handler settings.
entity_reference_selection.views:
type: mapping
label: 'View handler settings'
type: entity_reference_selection
label: 'Views selection handler settings'
mapping:
view:
type: mapping

View file

@ -191,6 +191,12 @@ views.field.entity_link:
text:
type: label
label: 'Text to display'
output_url_as_text:
type: boolean
label: 'Output the URL as text'
absolute:
type: boolean
label: 'Output an absolute link'
views.field.entity_link_delete:
type: views.field.entity_link

View file

@ -31,14 +31,6 @@ views.filter.combine:
type: string
label: 'Field'
views.filter_value.date:
type: views.filter_value.numeric
label: 'Date'
mapping:
type:
type: string
label: 'Type'
views.filter_value.groupby_numeric:
type: views.filter_value.numeric
label: 'Group by numeric'
@ -81,10 +73,31 @@ views.filter.string:
required:
type: boolean
label: 'Required'
placeholder:
type: string
label: 'Placeholder'
value:
type: string
label: 'Value'
views.filter.numeric:
type: views_filter
label: 'Numeric'
mapping:
expose:
type: mapping
label: 'Exposed'
mapping:
min_placeholder:
type: string
label: 'Min placeholder'
max_placeholder:
type: string
label: 'Max placeholder'
placeholder:
type: string
label: 'Placeholder'
views.filter_value.numeric:
type: mapping
label: 'Numeric'
@ -150,6 +163,10 @@ views.filter.language:
type: views.filter.in_operator
label: 'Language'
views.filter.latest_revision:
type: views_filter
label: 'Latest revision'
views.filter_value.date:
type: views.filter_value.numeric
label: 'Date'
@ -158,8 +175,8 @@ views.filter_value.date:
type: string
label: 'Type'
views.filter_value.datetime:
type: views.filter_value.numeric
views.filter.date:
type: views.filter.numeric
label: 'Date'
mapping:
type:

View file

@ -0,0 +1,232 @@
/**
* @file
* Handles AJAX fetching of views, including filter submission and response.
*/
(function($, Drupal, drupalSettings) {
/**
* Attaches the AJAX behavior to exposed filters forms and key View links.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches ajaxView functionality to relevant elements.
*/
Drupal.behaviors.ViewsAjaxView = {};
Drupal.behaviors.ViewsAjaxView.attach = function(context, settings) {
if (settings && settings.views && settings.views.ajaxViews) {
const {
views: { ajaxViews },
} = settings;
Object.keys(ajaxViews || {}).forEach(i => {
Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
});
}
};
Drupal.behaviors.ViewsAjaxView.detach = (context, settings, trigger) => {
if (trigger === 'unload') {
if (settings && settings.views && settings.views.ajaxViews) {
const {
views: { ajaxViews },
} = settings;
Object.keys(ajaxViews || {}).forEach(i => {
const selector = `.js-view-dom-id-${ajaxViews[i].view_dom_id}`;
if ($(selector, context).length) {
delete Drupal.views.instances[i];
delete settings.views.ajaxViews[i];
}
});
}
}
};
/**
* @namespace
*/
Drupal.views = {};
/**
* @type {object.<string, Drupal.views.ajaxView>}
*/
Drupal.views.instances = {};
/**
* Javascript object for a certain view.
*
* @constructor
*
* @param {object} settings
* Settings object for the ajax view.
* @param {string} settings.view_dom_id
* The DOM id of the view.
*/
Drupal.views.ajaxView = function(settings) {
const selector = `.js-view-dom-id-${settings.view_dom_id}`;
this.$view = $(selector);
// Retrieve the path to use for views' ajax.
let ajaxPath = drupalSettings.views.ajax_path;
// If there are multiple views this might've ended up showing up multiple
// times.
if (ajaxPath.constructor.toString().indexOf('Array') !== -1) {
ajaxPath = ajaxPath[0];
}
// Check if there are any GET parameters to send to views.
let queryString = window.location.search || '';
if (queryString !== '') {
// Remove the question mark and Drupal path component if any.
queryString = queryString
.slice(1)
.replace(/q=[^&]+&?|&?render=[^&]+/, '');
if (queryString !== '') {
// If there is a '?' in ajaxPath, clean url are on and & should be
// used to add parameters.
queryString = (/\?/.test(ajaxPath) ? '&' : '?') + queryString;
}
}
this.element_settings = {
url: ajaxPath + queryString,
submit: settings,
setClick: true,
event: 'click',
selector,
progress: { type: 'fullscreen' },
};
this.settings = settings;
// Add the ajax to exposed forms.
this.$exposed_form = $(
`form#views-exposed-form-${settings.view_name.replace(
/_/g,
'-',
)}-${settings.view_display_id.replace(/_/g, '-')}`,
);
this.$exposed_form
.once('exposed-form')
.each($.proxy(this.attachExposedFormAjax, this));
// Add the ajax to pagers.
this.$view
// Don't attach to nested views. Doing so would attach multiple behaviors
// to a given element.
.filter($.proxy(this.filterNestedViews, this))
.once('ajax-pager')
.each($.proxy(this.attachPagerAjax, this));
// Add a trigger to update this view specifically. In order to trigger a
// refresh use the following code.
//
// @code
// $('.view-name').trigger('RefreshView');
// @endcode
const selfSettings = $.extend({}, this.element_settings, {
event: 'RefreshView',
base: this.selector,
element: this.$view.get(0),
});
this.refreshViewAjax = Drupal.ajax(selfSettings);
};
/**
* @method
*/
Drupal.views.ajaxView.prototype.attachExposedFormAjax = function() {
const that = this;
this.exposedFormAjax = [];
// Exclude the reset buttons so no AJAX behaviours are bound. Many things
// break during the form reset phase if using AJAX.
$('input[type=submit], input[type=image]', this.$exposed_form)
.not('[data-drupal-selector=edit-reset]')
.each(function(index) {
const selfSettings = $.extend({}, that.element_settings, {
base: $(this).attr('id'),
element: this,
});
that.exposedFormAjax[index] = Drupal.ajax(selfSettings);
});
};
/**
* @return {bool}
* If there is at least one parent with a view class return false.
*/
Drupal.views.ajaxView.prototype.filterNestedViews = function() {
// If there is at least one parent with a view class, this view
// is nested (e.g., an attachment). Bail.
return !this.$view.parents('.view').length;
};
/**
* Attach the ajax behavior to each link.
*/
Drupal.views.ajaxView.prototype.attachPagerAjax = function() {
this.$view
.find(
'ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a',
)
.each($.proxy(this.attachPagerLinkAjax, this));
};
/**
* Attach the ajax behavior to a singe link.
*
* @param {string} [id]
* The ID of the link.
* @param {HTMLElement} link
* The link element.
*/
Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function(id, link) {
const $link = $(link);
const viewData = {};
const href = $link.attr('href');
// Construct an object using the settings defaults and then overriding
// with data specific to the link.
$.extend(
viewData,
this.settings,
Drupal.Views.parseQueryString(href),
// Extract argument data from the URL.
Drupal.Views.parseViewArgs(href, this.settings.view_base_path),
);
const selfSettings = $.extend({}, this.element_settings, {
submit: viewData,
base: false,
element: link,
});
this.pagerAjax = Drupal.ajax(selfSettings);
};
/**
* Views scroll to top ajax command.
*
* @param {Drupal.Ajax} [ajax]
* A {@link Drupal.ajax} object.
* @param {object} response
* Ajax response.
* @param {string} response.selector
* Selector to use.
*/
Drupal.AjaxCommands.prototype.viewsScrollTop = function(ajax, response) {
// Scroll to the top of the view. This will allow users
// to browse newly loaded content after e.g. clicking a pager
// link.
const offset = $(response.selector).offset();
// We can't guarantee that the scrollable object should be
// the body, as the view could be embedded in something
// more complex such as a modal popup. Recurse up the DOM
// and scroll the first element that has a non-zero top.
let scrollTarget = response.selector;
while ($(scrollTarget).scrollTop() === 0 && $(scrollTarget).parent()) {
scrollTarget = $(scrollTarget).parent();
}
// Only scroll upward.
if (offset.top - 10 < $(scrollTarget).scrollTop()) {
$(scrollTarget).animate({ scrollTop: offset.top - 10 }, 500);
}
};
})(jQuery, Drupal, drupalSettings);

View file

@ -1,205 +1,129 @@
/**
* @file
* Handles AJAX fetching of views, including filter submission and response.
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal, drupalSettings) {
'use strict';
/**
* Attaches the AJAX behavior to exposed filters forms and key View links.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches ajaxView functionality to relevant elements.
*/
Drupal.behaviors.ViewsAjaxView = {};
Drupal.behaviors.ViewsAjaxView.attach = function () {
if (drupalSettings && drupalSettings.views && drupalSettings.views.ajaxViews) {
var ajaxViews = drupalSettings.views.ajaxViews;
for (var i in ajaxViews) {
if (ajaxViews.hasOwnProperty(i)) {
Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
}
Drupal.behaviors.ViewsAjaxView.attach = function (context, settings) {
if (settings && settings.views && settings.views.ajaxViews) {
var ajaxViews = settings.views.ajaxViews;
Object.keys(ajaxViews || {}).forEach(function (i) {
Drupal.views.instances[i] = new Drupal.views.ajaxView(ajaxViews[i]);
});
}
};
Drupal.behaviors.ViewsAjaxView.detach = function (context, settings, trigger) {
if (trigger === 'unload') {
if (settings && settings.views && settings.views.ajaxViews) {
var ajaxViews = settings.views.ajaxViews;
Object.keys(ajaxViews || {}).forEach(function (i) {
var selector = '.js-view-dom-id-' + ajaxViews[i].view_dom_id;
if ($(selector, context).length) {
delete Drupal.views.instances[i];
delete settings.views.ajaxViews[i];
}
});
}
}
};
/**
* @namespace
*/
Drupal.views = {};
/**
* @type {object.<string, Drupal.views.ajaxView>}
*/
Drupal.views.instances = {};
/**
* Javascript object for a certain view.
*
* @constructor
*
* @param {object} settings
* Settings object for the ajax view.
* @param {string} settings.view_dom_id
* The DOM id of the view.
*/
Drupal.views.ajaxView = function (settings) {
var selector = '.js-view-dom-id-' + settings.view_dom_id;
this.$view = $(selector);
// Retrieve the path to use for views' ajax.
var ajax_path = drupalSettings.views.ajax_path;
var ajaxPath = drupalSettings.views.ajax_path;
// If there are multiple views this might've ended up showing up multiple
// times.
if (ajax_path.constructor.toString().indexOf('Array') !== -1) {
ajax_path = ajax_path[0];
if (ajaxPath.constructor.toString().indexOf('Array') !== -1) {
ajaxPath = ajaxPath[0];
}
// Check if there are any GET parameters to send to views.
var queryString = window.location.search || '';
if (queryString !== '') {
// Remove the question mark and Drupal path component if any.
queryString = queryString.slice(1).replace(/q=[^&]+&?|&?render=[^&]+/, '');
if (queryString !== '') {
// If there is a '?' in ajax_path, clean url are on and & should be
// used to add parameters.
queryString = ((/\?/.test(ajax_path)) ? '&' : '?') + queryString;
queryString = (/\?/.test(ajaxPath) ? '&' : '?') + queryString;
}
}
this.element_settings = {
url: ajax_path + queryString,
url: ajaxPath + queryString,
submit: settings,
setClick: true,
event: 'click',
selector: selector,
progress: {type: 'fullscreen'}
progress: { type: 'fullscreen' }
};
this.settings = settings;
// Add the ajax to exposed forms.
this.$exposed_form = $('form#views-exposed-form-' + settings.view_name.replace(/_/g, '-') + '-' + settings.view_display_id.replace(/_/g, '-'));
this.$exposed_form.once('exposed-form').each($.proxy(this.attachExposedFormAjax, this));
// Add the ajax to pagers.
this.$view
// Don't attach to nested views. Doing so would attach multiple behaviors
// to a given element.
.filter($.proxy(this.filterNestedViews, this))
.once('ajax-pager').each($.proxy(this.attachPagerAjax, this));
this.$view.filter($.proxy(this.filterNestedViews, this)).once('ajax-pager').each($.proxy(this.attachPagerAjax, this));
// Add a trigger to update this view specifically. In order to trigger a
// refresh use the following code.
//
// @code
// $('.view-name').trigger('RefreshView');
// @endcode
var self_settings = $.extend({}, this.element_settings, {
var selfSettings = $.extend({}, this.element_settings, {
event: 'RefreshView',
base: this.selector,
element: this.$view.get(0)
});
this.refreshViewAjax = Drupal.ajax(self_settings);
this.refreshViewAjax = Drupal.ajax(selfSettings);
};
/**
* @method
*/
Drupal.views.ajaxView.prototype.attachExposedFormAjax = function () {
var that = this;
this.exposedFormAjax = [];
// Exclude the reset buttons so no AJAX behaviours are bound. Many things
// break during the form reset phase if using AJAX.
$('input[type=submit], input[type=image]', this.$exposed_form).not('[data-drupal-selector=edit-reset]').each(function (index) {
var self_settings = $.extend({}, that.element_settings, {
var selfSettings = $.extend({}, that.element_settings, {
base: $(this).attr('id'),
element: this
});
that.exposedFormAjax[index] = Drupal.ajax(self_settings);
that.exposedFormAjax[index] = Drupal.ajax(selfSettings);
});
};
/**
* @return {bool}
* If there is at least one parent with a view class return false.
*/
Drupal.views.ajaxView.prototype.filterNestedViews = function () {
// If there is at least one parent with a view class, this view
// is nested (e.g., an attachment). Bail.
return !this.$view.parents('.view').length;
};
/**
* Attach the ajax behavior to each link.
*/
Drupal.views.ajaxView.prototype.attachPagerAjax = function () {
this.$view.find('ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a')
.each($.proxy(this.attachPagerLinkAjax, this));
this.$view.find('ul.js-pager__items > li > a, th.views-field a, .attachment .views-summary a').each($.proxy(this.attachPagerLinkAjax, this));
};
/**
* Attach the ajax behavior to a singe link.
*
* @param {string} [id]
* The ID of the link.
* @param {HTMLElement} link
* The link element.
*/
Drupal.views.ajaxView.prototype.attachPagerLinkAjax = function (id, link) {
var $link = $(link);
var viewData = {};
var href = $link.attr('href');
// Construct an object using the settings defaults and then overriding
// with data specific to the link.
$.extend(
viewData,
this.settings,
Drupal.Views.parseQueryString(href),
// Extract argument data from the URL.
Drupal.Views.parseViewArgs(href, this.settings.view_base_path)
);
var self_settings = $.extend({}, this.element_settings, {
$.extend(viewData, this.settings, Drupal.Views.parseQueryString(href), Drupal.Views.parseViewArgs(href, this.settings.view_base_path));
var selfSettings = $.extend({}, this.element_settings, {
submit: viewData,
base: false,
element: link
});
this.pagerAjax = Drupal.ajax(self_settings);
this.pagerAjax = Drupal.ajax(selfSettings);
};
/**
* Views scroll to top ajax command.
*
* @param {Drupal.Ajax} [ajax]
* A {@link Drupal.ajax} object.
* @param {object} response
* Ajax response.
* @param {string} response.selector
* Selector to use.
*/
Drupal.AjaxCommands.prototype.viewsScrollTop = function (ajax, response) {
// Scroll to the top of the view. This will allow users
// to browse newly loaded content after e.g. clicking a pager
// link.
var offset = $(response.selector).offset();
// We can't guarantee that the scrollable object should be
// the body, as the view could be embedded in something
// more complex such as a modal popup. Recurse up the DOM
// and scroll the first element that has a non-zero top.
var scrollTarget = response.selector;
while ($(scrollTarget).scrollTop() === 0 && $(scrollTarget).parent()) {
scrollTarget = $(scrollTarget).parent();
}
// Only scroll upward.
if (offset.top - 10 < $(scrollTarget).scrollTop()) {
$(scrollTarget).animate({scrollTop: (offset.top - 10)}, 500);
$(scrollTarget).animate({ scrollTop: offset.top - 10 }, 500);
}
};
})(jQuery, Drupal, drupalSettings);
})(jQuery, Drupal, drupalSettings);

View file

@ -0,0 +1,112 @@
/**
* @file
* Some basic behaviors and utility functions for Views.
*/
(function($, Drupal, drupalSettings) {
/**
* @namespace
*/
Drupal.Views = {};
/**
* Helper function to parse a querystring.
*
* @param {string} query
* The querystring to parse.
*
* @return {object}
* A map of query parameters.
*/
Drupal.Views.parseQueryString = function(query) {
const args = {};
const pos = query.indexOf('?');
if (pos !== -1) {
query = query.substring(pos + 1);
}
let pair;
const pairs = query.split('&');
for (let i = 0; i < pairs.length; i++) {
pair = pairs[i].split('=');
// Ignore the 'q' path argument, if present.
if (pair[0] !== 'q' && pair[1]) {
args[
decodeURIComponent(pair[0].replace(/\+/g, ' '))
] = decodeURIComponent(pair[1].replace(/\+/g, ' '));
}
}
return args;
};
/**
* Helper function to return a view's arguments based on a path.
*
* @param {string} href
* The href to check.
* @param {string} viewPath
* The views path to check.
*
* @return {object}
* An object containing `view_args` and `view_path`.
*/
Drupal.Views.parseViewArgs = function(href, viewPath) {
const returnObj = {};
const path = Drupal.Views.getPath(href);
// Get viewPath url without baseUrl portion.
const viewHref = Drupal.url(viewPath).substring(
drupalSettings.path.baseUrl.length,
);
// Ensure we have a correct path.
if (viewHref && path.substring(0, viewHref.length + 1) === `${viewHref}/`) {
returnObj.view_args = decodeURIComponent(
path.substring(viewHref.length + 1, path.length),
);
returnObj.view_path = path;
}
return returnObj;
};
/**
* Strip off the protocol plus domain from an href.
*
* @param {string} href
* The href to strip.
*
* @return {string}
* The href without the protocol and domain.
*/
Drupal.Views.pathPortion = function(href) {
// Remove e.g. http://example.com if present.
const protocol = window.location.protocol;
if (href.substring(0, protocol.length) === protocol) {
// 2 is the length of the '//' that normally follows the protocol.
href = href.substring(href.indexOf('/', protocol.length + 2));
}
return href;
};
/**
* Return the Drupal path portion of an href.
*
* @param {string} href
* The href to check.
*
* @return {string}
* An internal path.
*/
Drupal.Views.getPath = function(href) {
href = Drupal.Views.pathPortion(href);
href = href.substring(drupalSettings.path.baseUrl.length, href.length);
// 3 is the length of the '?q=' added to the url without clean urls.
if (href.substring(0, 3) === '?q=') {
href = href.substring(3, href.length);
}
const chars = ['#', '?', '&'];
for (let i = 0; i < chars.length; i++) {
if (href.indexOf(chars[i]) > -1) {
href = href.substr(0, href.indexOf(chars[i]));
}
}
return href;
};
})(jQuery, Drupal, drupalSettings);

View file

@ -1,37 +1,24 @@
/**
* @file
* Some basic behaviors and utility functions for Views.
*/
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal, drupalSettings) {
'use strict';
/**
* @namespace
*/
Drupal.Views = {};
/**
* Helper function to parse a querystring.
*
* @param {string} query
* The querystring to parse.
*
* @return {object}
* A map of query parameters.
*/
Drupal.Views.parseQueryString = function (query) {
var args = {};
var pos = query.indexOf('?');
if (pos !== -1) {
query = query.substring(pos + 1);
}
var pair;
var pair = void 0;
var pairs = query.split('&');
for (var i = 0; i < pairs.length; i++) {
pair = pairs[i].split('=');
// Ignore the 'q' path argument, if present.
if (pair[0] !== 'q' && pair[1]) {
args[decodeURIComponent(pair[0].replace(/\+/g, ' '))] = decodeURIComponent(pair[1].replace(/\+/g, ' '));
}
@ -39,23 +26,12 @@
return args;
};
/**
* Helper function to return a view's arguments based on a path.
*
* @param {string} href
* The href to check.
* @param {string} viewPath
* The views path to check.
*
* @return {object}
* An object containing `view_args` and `view_path`.
*/
Drupal.Views.parseViewArgs = function (href, viewPath) {
var returnObj = {};
var path = Drupal.Views.getPath(href);
// Get viewPath url without baseUrl portion.
var viewHref = Drupal.url(viewPath).substring(drupalSettings.path.baseUrl.length);
// Ensure we have a correct path.
if (viewHref && path.substring(0, viewHref.length + 1) === viewHref + '/') {
returnObj.view_args = decodeURIComponent(path.substring(viewHref.length + 1, path.length));
returnObj.view_path = path;
@ -63,38 +39,18 @@
return returnObj;
};
/**
* Strip off the protocol plus domain from an href.
*
* @param {string} href
* The href to strip.
*
* @return {string}
* The href without the protocol and domain.
*/
Drupal.Views.pathPortion = function (href) {
// Remove e.g. http://example.com if present.
var protocol = window.location.protocol;
if (href.substring(0, protocol.length) === protocol) {
// 2 is the length of the '//' that normally follows the protocol.
href = href.substring(href.indexOf('/', protocol.length + 2));
}
return href;
};
/**
* Return the Drupal path portion of an href.
*
* @param {string} href
* The href to check.
*
* @return {string}
* An internal path.
*/
Drupal.Views.getPath = function (href) {
href = Drupal.Views.pathPortion(href);
href = href.substring(drupalSettings.path.baseUrl.length, href.length);
// 3 is the length of the '?q=' added to the url without clean urls.
if (href.substring(0, 3) === '?q=') {
href = href.substring(3, href.length);
}
@ -106,5 +62,4 @@
}
return href;
};
})(jQuery, Drupal, drupalSettings);
})(jQuery, Drupal, drupalSettings);

View file

@ -31,7 +31,6 @@ class Analyzer {
$this->moduleHandler = $module_handler;
}
/**
* Analyzes a review and return the results.
*
@ -52,7 +51,8 @@ class Analyzer {
/**
* Formats the analyze result into a message string.
*
* This is based upon the format of drupal_set_message which uses separate
* This is based upon the format of
* \Drupal\Core\Messenger\MessengerInterface::addMessage() which uses separate
* boxes for "ok", "warning" and "error".
*/
public function formatMessages(array $messages) {
@ -77,7 +77,7 @@ class Analyzer {
'#theme' => 'item_list',
'#items' => $messages,
];
$message = drupal_render($item_list);
$message = \Drupal::service('renderer')->render($item_list);
}
elseif ($messages) {
$message = array_shift($messages);

View file

@ -2,7 +2,6 @@
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views access plugins.
*

View file

@ -111,7 +111,7 @@ class ViewsDisplay extends ViewsPluginAnnotationBase {
/**
* The theme function used to render the display's output.
*
* @return string
* @var string
*/
public $theme;

View file

@ -50,7 +50,7 @@ class ViewsPager extends ViewsPluginAnnotationBase {
/**
* The theme function used to render the pager's output.
*
* @return string
* @var string
*/
public $theme;

View file

@ -50,7 +50,7 @@ class ViewsRow extends ViewsPluginAnnotationBase {
/**
* The theme function used to render the row output.
*
* @return string
* @var string
*/
public $theme;

View file

@ -132,7 +132,20 @@ class ViewAjaxController implements ContainerInjectionInterface {
// Remove all of this stuff from the query of the request so it doesn't
// end up in pagers and tablesort URLs.
foreach (['view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER] as $key) {
// @todo Remove this parsing once these are removed from the request in
// https://www.drupal.org/node/2504709.
foreach ([
'view_name',
'view_display_id',
'view_args',
'view_path',
'view_dom_id',
'pager_element',
'view_base_path',
AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER,
FormBuilderInterface::AJAX_FORM_REQUEST,
MainContentViewSubscriber::WRAPPER_FORMAT,
] as $key) {
$request->query->remove($key);
$request->request->remove($key);
}
@ -142,7 +155,7 @@ class ViewAjaxController implements ContainerInjectionInterface {
throw new NotFoundHttpException();
}
$view = $this->executableFactory->get($entity);
if ($view && $view->access($display_id)) {
if ($view && $view->access($display_id) && $view->setDisplay($display_id) && $view->display_handler->ajaxEnabled()) {
$response->setView($view);
// Fix the current path for paging.
if (!empty($path)) {
@ -152,6 +165,7 @@ class ViewAjaxController implements ContainerInjectionInterface {
// Add all POST data, because AJAX is always a post and many things,
// such as tablesorts, exposed filters and paging assume GET.
$request_all = $request->request->all();
unset($request_all['ajax_page_state']);
$query_all = $request->query->all();
$request->query->replace($request_all + $query_all);
@ -159,13 +173,7 @@ class ViewAjaxController implements ContainerInjectionInterface {
// @see the redirect.destination service.
$origin_destination = $path;
// Remove some special parameters you never want to have part of the
// destination query.
$used_query_parameters = $request->query->all();
// @todo Remove this parsing once these are removed from the request in
// https://www.drupal.org/node/2504709.
unset($used_query_parameters[FormBuilderInterface::AJAX_FORM_REQUEST], $used_query_parameters[MainContentViewSubscriber::WRAPPER_FORMAT], $used_query_parameters['ajax_page_state']);
$query = UrlHelper::buildQuery($used_query_parameters);
if ($query != '') {
$origin_destination .= '?' . $query;
@ -181,7 +189,7 @@ class ViewAjaxController implements ContainerInjectionInterface {
$view->dom_id = $dom_id;
$context = new RenderContext();
$preview = $this->renderer->executeInRenderContext($context, function() use ($view, $display_id, $args) {
$preview = $this->renderer->executeInRenderContext($context, function () use ($view, $display_id, $args) {
return $view->preview($display_id, $args);
});
if (!$context->isEmpty()) {

View file

@ -80,7 +80,7 @@ class DisplayPluginCollection extends DefaultLazyPluginCollection {
// display plugin isn't found.
catch (PluginException $e) {
$message = $e->getMessage();
drupal_set_message(t('@message', ['@message' => $message]), 'warning');
\Drupal::messenger()->addWarning(t('@message', ['@message' => $message]));
}
// If no plugin instance has been created, return NULL.

View file

@ -53,7 +53,6 @@ class View extends RenderElement {
// possible to manipulate the $element.
$view->element['#pre_rendered'] = TRUE;
if (isset($element['#response'])) {
$view->setResponse($element['#response']);
}

View file

@ -28,15 +28,54 @@ class TranslationLanguageRenderer extends EntityTranslationRendererBase {
if (!$this->languageManager->isMultilingual() || !$this->entityType->hasKey('langcode')) {
return;
}
$langcode_key = $this->entityType->getKey('langcode');
$storage = \Drupal::entityManager()->getStorage($this->entityType->id());
if ($table = $storage->getTableMapping()->getFieldTableName($langcode_key)) {
$table_alias = $query->ensureTable($table, $relationship);
$langcode_table = $this->getLangcodeTable($query, $relationship);
if ($langcode_table) {
/** @var \Drupal\views\Plugin\views\query\Sql $query */
$table_alias = $query->ensureTable($langcode_table, $relationship);
$langcode_key = $this->entityType->getKey('langcode');
$this->langcodeAlias = $query->addField($table_alias, $langcode_key);
}
}
/**
* Returns the name of the table holding the "langcode" field.
*
* @param \Drupal\views\Plugin\views\query\QueryPluginBase $query
* The query being executed.
* @param string $relationship
* The relationship used by the entity type.
*
* @return string
* A table name.
*/
protected function getLangcodeTable(QueryPluginBase $query, $relationship) {
/** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
$storage = \Drupal::entityTypeManager()->getStorage($this->entityType->id());
$langcode_key = $this->entityType->getKey('langcode');
$langcode_table = $storage->getTableMapping()->getFieldTableName($langcode_key);
// If the entity type is revisionable, we need to take into account views of
// entity revisions. Usually the view will use the entity data table as the
// query base table, however, in case of an entity revision view, we need to
// use the revision table or the revision data table, depending on which one
// is being used as query base table.
if ($this->entityType->isRevisionable()) {
$query_base_table = isset($query->relationships[$relationship]['base']) ?
$query->relationships[$relationship]['base'] :
$this->view->storage->get('base_table');
$revision_table = $storage->getRevisionTable();
$revision_data_table = $storage->getRevisionDataTable();
if ($query_base_table === $revision_table) {
$langcode_table = $revision_table;
}
elseif ($query_base_table === $revision_data_table) {
$langcode_table = $revision_data_table;
}
}
return $langcode_table;
}
/**
* {@inheritdoc}
*/

View file

@ -5,8 +5,11 @@ namespace Drupal\views\Entity;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\views\Plugin\DependentWithRemovalPluginInterface;
use Drupal\views\Views;
use Drupal\views\ViewEntityInterface;
@ -16,9 +19,14 @@ use Drupal\views\ViewEntityInterface;
* @ConfigEntityType(
* id = "view",
* label = @Translation("View", context = "View entity type"),
* handlers = {
* "access" = "Drupal\views\ViewAccessControlHandler"
* },
* label_collection = @Translation("Views", context = "View entity type"),
* label_singular = @Translation("view", context = "View entity type"),
* label_plural = @Translation("views", context = "View entity type"),
* label_count = @PluralTranslation(
* singular = "@count view",
* plural = "@count views",
* context = "View entity type",
* ),
* admin_permission = "administer views",
* entity_keys = {
* "id" = "id",
@ -245,6 +253,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;
@ -290,10 +299,13 @@ class View extends ConfigEntityBase implements ViewEntityInterface {
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
$displays = $this->get('display');
$this->fixTableNames($displays);
// Sort the displays.
$display = $this->get('display');
ksort($display);
$this->set('display', ['default' => $display['default']] + $display);
ksort($displays);
$this->set('display', ['default' => $displays['default']] + $displays);
// @todo Check whether isSyncing is needed.
if (!$this->isSyncing()) {
@ -301,6 +313,47 @@ class View extends ConfigEntityBase implements ViewEntityInterface {
}
}
/**
* Fixes table names for revision metadata fields of revisionable entities.
*
* Views for revisionable entity types using revision metadata fields might
* be using the wrong table to retrieve the fields after system_update_8300
* has moved them correctly to the revision table. This method updates the
* views to use the correct tables.
*
* @param array &$displays
* An array containing display handlers of a view.
*
* @deprecated in Drupal 8.3.0, will be removed in Drupal 9.0.0.
*
* @see https://www.drupal.org/node/2831499
*/
private function fixTableNames(array &$displays) {
// Fix wrong table names for entity revision metadata fields.
foreach ($displays as $display => $display_data) {
if (isset($display_data['display_options']['fields'])) {
foreach ($display_data['display_options']['fields'] as $property_name => $property_data) {
if (isset($property_data['entity_type']) && isset($property_data['field']) && isset($property_data['table'])) {
$entity_type = $this->entityTypeManager()->getDefinition($property_data['entity_type']);
// We need to update the table name only for revisionable entity
// types, otherwise the view is already using the correct table.
if (($entity_type instanceof ContentEntityTypeInterface) && is_subclass_of($entity_type->getClass(), FieldableEntityInterface::class) && $entity_type->isRevisionable()) {
$revision_metadata_fields = $entity_type->getRevisionMetadataKeys();
// @see \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout()
$revision_table = $entity_type->getRevisionTable() ?: $entity_type->id() . '_revision';
// Check if this is a revision metadata field and if it uses the
// wrong table.
if (in_array($property_data['field'], $revision_metadata_fields) && $property_data['table'] != $revision_table) {
$displays[$display]['display_options']['fields'][$property_name]['table'] = $revision_table;
}
}
}
}
}
}
}
/**
* Fills in the cache metadata of this view.
*
@ -345,7 +398,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();
}
@ -378,7 +431,7 @@ class View extends ConfigEntityBase implements ViewEntityInterface {
'position' => 0,
'display_options' => [],
],
]
],
];
}
@ -414,7 +467,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());
}
@ -468,4 +521,61 @@ class View extends ConfigEntityBase implements ViewEntityInterface {
\Drupal::service('cache_tags.invalidator')->invalidateTags($tags);
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
$changed = FALSE;
// Don't intervene if the views module is removed.
if (isset($dependencies['module']) && in_array('views', $dependencies['module'])) {
return FALSE;
}
// If the base table for the View is provided by a module being removed, we
// delete the View because this is not something that can be fixed manually.
$views_data = Views::viewsData();
$base_table = $this->get('base_table');
$base_table_data = $views_data->get($base_table);
if (!empty($base_table_data['table']['provider']) && in_array($base_table_data['table']['provider'], $dependencies['module'])) {
return FALSE;
}
$current_display = $this->getExecutable()->current_display;
$handler_types = Views::getHandlerTypes();
// Find all the handlers and check whether they want to do something on
// dependency removal.
foreach ($this->display as $display_id => $display_plugin_base) {
$this->getExecutable()->setDisplay($display_id);
$display = $this->getExecutable()->getDisplay();
foreach (array_keys($handler_types) as $handler_type) {
$handlers = $display->getHandlers($handler_type);
foreach ($handlers as $handler_id => $handler) {
if ($handler instanceof DependentWithRemovalPluginInterface) {
if ($handler->onDependencyRemoval($dependencies)) {
// Remove the handler and indicate we made changes.
unset($this->display[$display_id]['display_options'][$handler_types[$handler_type]['plural']][$handler_id]);
$changed = TRUE;
}
}
}
}
}
// Disable the View if we made changes.
// @todo https://www.drupal.org/node/2832558 Give better feedback for
// disabled config.
if ($changed) {
// Force a recalculation of the dependencies if we made changes.
$this->getExecutable()->current_display = NULL;
$this->calculateDependencies();
$this->disable();
}
$this->getExecutable()->setDisplay($current_display);
return $changed;
}
}

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()) {
@ -200,7 +207,7 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
$data[$base_table]['table']['join'][$data_table] = [
'left_field' => $base_field,
'field' => $base_field,
'type' => 'INNER'
'type' => 'INNER',
];
$data[$data_table]['table']['group'] = $this->entityType->getLabel();
$data[$data_table]['table']['provider'] = $this->entityType->getProvider();
@ -236,9 +243,19 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
'type' => 'INNER',
];
}
// Add a filter for showing only the latest revisions of an entity.
$data[$revision_table]['latest_revision'] = [
'title' => $this->t('Is Latest Revision'),
'help' => $this->t('Restrict the view to only revisions that are the latest revision of their entity.'),
'filter' => ['id' => 'latest_revision'],
];
}
$this->addEntityLinks($data[$base_table]);
if ($views_revision_base_table) {
$this->addEntityLinks($data[$views_revision_base_table]);
}
// Load all typed data definitions of all fields. This should cover each of
// the entity base, revision, data tables.
@ -300,7 +317,7 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
// Add the entity type key to each table generated.
$entity_type_id = $this->entityType->id();
array_walk($data, function(&$table_data) use ($entity_type_id){
array_walk($data, function (&$table_data) use ($entity_type_id) {
$table_data['table']['entity type'] = $entity_type_id;
});
@ -371,13 +388,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;
}
}

View file

@ -12,6 +12,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the views exposed form.
*
* @internal
*/
class ViewsExposedForm extends FormBase {
@ -101,7 +103,7 @@ class ViewsExposedForm extends FormBase {
}
$form['actions'] = [
'#type' => 'actions'
'#type' => 'actions',
];
$form['actions']['submit'] = [
// Prevent from showing up in \Drupal::request()->query.
@ -181,7 +183,7 @@ class ViewsExposedForm extends FormBase {
// https://www.drupal.org/node/342316 is resolved.
$checked = Checkboxes::getCheckedCheckboxes($value);
foreach ($checked as $option_id) {
$view->exposed_raw_input[$option_id] = $value[$option_id];
$view->exposed_raw_input[$key][] = $value[$option_id];
}
}
else {

View file

@ -2,6 +2,7 @@
namespace Drupal\views;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\HandlerBase;
@ -268,8 +269,8 @@ class ManyToOneHelper {
$options['group'] = 0;
}
// add_condition determines whether a single expression is enough(FALSE) or the
// conditions should be added via an db_or()/db_and() (TRUE).
// If $add_condition is set to FALSE, a single expression is enough. If it
// is set to TRUE, conditions will be added.
$add_condition = TRUE;
if ($operator == 'not') {
$value = NULL;
@ -326,7 +327,7 @@ class ManyToOneHelper {
if ($add_condition) {
$field = $this->handler->realField;
$clause = $operator == 'or' ? db_or() : db_and();
$clause = $operator == 'or' ? new Condition('OR') : new Condition('AND');
foreach ($this->handler->tableAliases as $value => $alias) {
$clause->condition("$alias.$field", $value);
}

View file

@ -1,6 +1,7 @@
<?php
namespace Drupal\views\Plugin\Block;
use Drupal\Core\Cache\Cache;
/**
@ -24,9 +25,23 @@ class ViewsExposedFilterBlock extends ViewsBlockBase {
/**
* {@inheritdoc}
*
* @return array
* A renderable array representing the content of the block with additional
* context of current view and display ID.
*/
public function build() {
$output = $this->view->display_handler->viewExposedFormBlocks();
// Provide the context for block build and block view alter hooks.
// \Drupal\views\Plugin\Block\ViewsBlock::build() adds the same context in
// \Drupal\views\ViewExecutable::buildRenderable() using
// \Drupal\views\Plugin\views\display\DisplayPluginBase::buildRenderable().
if (is_array($output) && !empty($output)) {
$output += [
'#view' => $this->view,
'#display_id' => $this->displayID,
];
}
// Before returning the block output, convert it to a renderable array with
// contextual links.

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\views\Plugin;
/**
* Provides an interface for a plugin that has dependencies that can be removed.
*
* @ingroup views_plugins
*/
interface DependentWithRemovalPluginInterface {
/**
* Allows a plugin to define whether it should be removed.
*
* If this method returns TRUE then the plugin should be removed.
*
* @param array $dependencies
* An array of dependencies that will be deleted keyed by dependency type.
* Dependency types are, for example, entity, module and theme.
*
* @return bool
* TRUE if the plugin instance should be removed.
*
* @see \Drupal\Core\Config\Entity\ConfigDependencyManager
* @see \Drupal\Core\Config\ConfigEntityBase::preDelete()
* @see \Drupal\Core\Config\ConfigManager::uninstall()
* @see \Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval()
*/
public function onDependencyRemoval(array $dependencies);
}

View file

@ -11,6 +11,7 @@ use Drupal\views\Views;
* The derivatives store all base table plugin information.
*/
class DefaultWizardDeriver extends DeriverBase {
/**
* {@inheritdoc}
*/
@ -25,7 +26,7 @@ class DefaultWizardDeriver extends DeriverBase {
'id' => 'standard',
'base_table' => $table,
'title' => $views_info['table']['base']['title'],
'class' => 'Drupal\views\Plugin\views\wizard\Standard'
'class' => 'Drupal\views\Plugin\views\wizard\Standard',
];
}
}

View file

@ -91,8 +91,8 @@ class ViewsExposedFilterBlock implements ContainerDeriverInterface {
'config_dependencies' => [
'config' => [
$view->getConfigDependencyName(),
]
]
],
],
];
$this->derivatives[$delta] += $base_plugin_definition;
}

View file

@ -2,17 +2,12 @@
namespace Drupal\views\Plugin\EntityReferenceSelection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase;
use Drupal\Core\Entity\EntityReferenceSelection\SelectionTrait;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'selection' entity_reference.
@ -24,80 +19,37 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* weight = 0
* )
*/
class ViewsSelection extends PluginBase implements SelectionInterface, ContainerFactoryPluginInterface {
class ViewsSelection extends SelectionPluginBase implements ContainerFactoryPluginInterface {
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The module handler service.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* Constructs a new SelectionBase 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 service.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler service.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->moduleHandler = $module_handler;
$this->currentUser = $current_user;
}
/**
* {@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('module_handler'),
$container->get('current_user')
);
}
use SelectionTrait;
/**
* The loaded View object.
*
* @var \Drupal\views\ViewExecutable;
* @var \Drupal\views\ViewExecutable
*/
protected $view;
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'view' => [
'view_name' => NULL,
'display_name' => NULL,
'arguments' => [],
],
] + parent::defaultConfiguration();
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$selection_handler_settings = $this->configuration['handler_settings'];
$view_settings = !empty($selection_handler_settings['view']) ? $selection_handler_settings['view'] : [];
$form = parent::buildConfigurationForm($form, $form_state);
$view_settings = $this->getConfiguration()['view'];
$displays = Views::getApplicableViews('entity_reference_display');
// Filter views that list the entity type we want, and group the separate
// displays by view.
@ -156,16 +108,6 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* Initializes a view.
*
@ -184,14 +126,13 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
* Return TRUE if the view was initialized, FALSE otherwise.
*/
protected function initializeView($match = NULL, $match_operator = 'CONTAINS', $limit = 0, $ids = NULL) {
$handler_settings = $this->configuration['handler_settings'];
$view_name = $handler_settings['view']['view_name'];
$display_name = $handler_settings['view']['display_name'];
$view_name = $this->getConfiguration()['view']['view_name'];
$display_name = $this->getConfiguration()['view']['display_name'];
// Check that the view is valid and the display still exists.
$this->view = Views::getView($view_name);
if (!$this->view || !$this->view->access($display_name)) {
drupal_set_message(t('The reference view %view_name cannot be found.', ['%view_name' => $view_name]), 'warning');
\Drupal::messenger()->addWarning(t('The reference view %view_name cannot be found.', ['%view_name' => $view_name]));
return FALSE;
}
$this->view->setDisplay($display_name);
@ -211,9 +152,8 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
* {@inheritdoc}
*/
public function getReferenceableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) {
$handler_settings = $this->configuration['handler_settings'];
$display_name = $handler_settings['view']['display_name'];
$arguments = $handler_settings['view']['arguments'];
$display_name = $this->getConfiguration()['view']['display_name'];
$arguments = $this->getConfiguration()['view']['arguments'];
$result = [];
if ($this->initializeView($match, $match_operator, $limit)) {
// Get the results.
@ -242,9 +182,8 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
* {@inheritdoc}
*/
public function validateReferenceableEntities(array $ids) {
$handler_settings = $this->configuration['handler_settings'];
$display_name = $handler_settings['view']['display_name'];
$arguments = $handler_settings['view']['arguments'];
$display_name = $this->getConfiguration()['view']['display_name'];
$arguments = $this->getConfiguration()['view']['arguments'];
$result = [];
if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) {
// Get the results.
@ -283,9 +222,4 @@ class ViewsSelection extends PluginBase implements SelectionInterface, Container
$form_state->setValueForElement($element, $value);
}
/**
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) { }
}

View file

@ -126,7 +126,6 @@ class ViewsMenuLink extends MenuLinkBase implements ContainerFactoryPluginInterf
return (bool) $this->loadView()->display_handler->getOption('menu')['expanded'];
}
/**
* {@inheritdoc}
*/

View file

@ -2,7 +2,7 @@
namespace Drupal\views\Plugin\views;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Form\FormStateInterface;
/**
@ -55,11 +55,11 @@ trait BrokenHandlerTrait {
foreach ($this->definition['original_configuration'] as $key => $value) {
if (is_scalar($value)) {
$items[] = SafeMarkup::format('@key: @value', ['@key' => $key, '@value' => $value]);
$items[] = new FormattableMarkup('@key: @value', ['@key' => $key, '@value' => $value]);
}
}
$description_bottom = t('Enabling the appropriate module will may solve this issue. Otherwise, check to see if there is a module update available.');
$description_bottom = t('Enabling the appropriate module may solve this issue. Otherwise, check to see if there is a module update available.');
$form['description'] = [
'#type' => 'container',

View file

@ -214,25 +214,25 @@ abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
* Transform a string by a certain method.
*
* @param $string
* The input you want to transform.
* The input you want to transform.
* @param $option
* How do you want to transform it, possible values:
* - upper: Uppercase the string.
* - lower: lowercase the string.
* - ucfirst: Make the first char uppercase.
* - ucwords: Make each word in the string uppercase.
* How do you want to transform it, possible values:
* - upper: Uppercase the string.
* - lower: lowercase the string.
* - ucfirst: Make the first char uppercase.
* - ucwords: Make each word in the string uppercase.
*
* @return string
* The transformed string.
* The transformed string.
*/
protected function caseTransform($string, $option) {
switch ($option) {
default:
return $string;
case 'upper':
return Unicode::strtoupper($string);
return mb_strtoupper($string);
case 'lower':
return Unicode::strtolower($string);
return mb_strtolower($string);
case 'ucfirst':
return Unicode::ucfirst($string);
case 'ucwords':
@ -313,6 +313,7 @@ abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
public function usesGroupBy() {
return TRUE;
}
/**
* Provide a form for aggregation settings.
*/
@ -350,80 +351,84 @@ abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
* If a handler has 'extra options' it will get a little settings widget and
* another form called extra_options.
*/
public function hasExtraOptions() { return FALSE; }
public function hasExtraOptions() {
return FALSE;
}
/**
* Provide defaults for the handler.
*/
public function defineExtraOptions(&$option) { }
public function defineExtraOptions(&$option) {}
/**
* Provide a form for setting options.
*/
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) { }
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Validate the options form.
*/
public function validateExtraOptionsForm($form, FormStateInterface $form_state) { }
public function validateExtraOptionsForm($form, FormStateInterface $form_state) {}
/**
* Perform any necessary changes to the form values prior to storage.
* There is no need for this function to actually store the data.
*/
public function submitExtraOptionsForm($form, FormStateInterface $form_state) { }
public function submitExtraOptionsForm($form, FormStateInterface $form_state) {}
/**
* Determine if a handler can be exposed.
*/
public function canExpose() { return FALSE; }
public function canExpose() {
return FALSE;
}
/**
* Set new exposed option defaults when exposed setting is flipped
* on.
*/
public function defaultExposeOptions() { }
public function defaultExposeOptions() {}
/**
* Get information about the exposed form for the form renderer.
*/
public function exposedInfo() { }
public function exposedInfo() {}
/**
* Render our chunk of the exposed handler form when selecting
*/
public function buildExposedForm(&$form, FormStateInterface $form_state) { }
public function buildExposedForm(&$form, FormStateInterface $form_state) {}
/**
* Validate the exposed handler form
*/
public function validateExposed(&$form, FormStateInterface $form_state) { }
public function validateExposed(&$form, FormStateInterface $form_state) {}
/**
* Submit the exposed handler form
*/
public function submitExposed(&$form, FormStateInterface $form_state) { }
public function submitExposed(&$form, FormStateInterface $form_state) {}
/**
* Form for exposed handler options.
*/
public function buildExposeForm(&$form, FormStateInterface $form_state) { }
public function buildExposeForm(&$form, FormStateInterface $form_state) {}
/**
* Validate the options form.
*/
public function validateExposeForm($form, FormStateInterface $form_state) { }
public function validateExposeForm($form, FormStateInterface $form_state) {}
/**
* Perform any necessary changes to the form exposes prior to storage.
* There is no need for this function to actually store the data.
*/
public function submitExposeForm($form, FormStateInterface $form_state) { }
public function submitExposeForm($form, FormStateInterface $form_state) {}
/**
* Shortcut to display the expose/hide button.
*/
public function showExposeButton(&$form, FormStateInterface $form_state) { }
public function showExposeButton(&$form, FormStateInterface $form_state) {}
/**
* Shortcut to display the exposed options form.
@ -477,7 +482,7 @@ abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
/**
* {@inheritdoc}
*/
public function postExecute(&$values) { }
public function postExecute(&$values) {}
/**
* Provides a unique placeholders for handlers.
@ -531,7 +536,7 @@ abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
/**
* {@inheritdoc}
*/
public function adminSummary() { }
public function adminSummary() {}
/**
* Determine if this item is 'exposed', meaning it provides form elements
@ -546,24 +551,32 @@ abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
/**
* Returns TRUE if the exposed filter works like a grouped filter.
*/
public function isAGroup() { return FALSE; }
public function isAGroup() {
return FALSE;
}
/**
* Define if the exposed input has to be submitted multiple times.
* This is TRUE when exposed filters grouped are using checkboxes as
* widgets.
*/
public function multipleExposedInput() { return FALSE; }
public function multipleExposedInput() {
return FALSE;
}
/**
* Take input from exposed handlers and assign to this handler, if necessary.
*/
public function acceptExposedInput($input) { return TRUE; }
public function acceptExposedInput($input) {
return TRUE;
}
/**
* If set to remember exposed input in the session, store it there.
*/
public function storeExposedInput($input, $status) { return TRUE; }
public function storeExposedInput($input, $status) {
return TRUE;
}
/**
* {@inheritdoc}
@ -587,7 +600,9 @@ abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
/**
* {@inheritdoc}
*/
public function validate() { return []; }
public function validate() {
return [];
}
/**
* {@inheritdoc}
@ -831,4 +846,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;
}
}

View file

@ -155,7 +155,9 @@ abstract class PluginBase extends ComponentPluginBase implements ContainerFactor
* @return array
* Returns the options of this handler/plugin.
*/
protected function defineOptions() { return []; }
protected function defineOptions() {
return [];
}
/**
* Fills up the options of the plugin with defaults.
@ -271,17 +273,17 @@ abstract class PluginBase extends ComponentPluginBase implements ContainerFactor
/**
* {@inheritdoc}
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) { }
public function validateOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) { }
public function submitOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
*/
public function query() { }
public function query() {}
/**
* {@inheritdoc}
@ -293,7 +295,9 @@ abstract class PluginBase extends ComponentPluginBase implements ContainerFactor
/**
* {@inheritdoc}
*/
public function validate() { return []; }
public function validate() {
return [];
}
/**
* {@inheritdoc}
@ -351,7 +355,7 @@ abstract class PluginBase extends ComponentPluginBase implements ContainerFactor
foreach ($tokens as $token => $replacement) {
// Twig wants a token replacement array stripped of curly-brackets.
// Some Views tokens come with curly-braces, others do not.
//@todo: https://www.drupal.org/node/2544392
// @todo: https://www.drupal.org/node/2544392
if (strpos($token, '{{') !== FALSE) {
// Twig wants a token replacement array stripped of curly-brackets.
$token = trim(str_replace(['{{', '}}'], '', $token));
@ -364,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])) {
@ -396,7 +400,7 @@ abstract class PluginBase extends ComponentPluginBase implements ContainerFactor
'#post_render' => [
function ($children, $elements) {
return Xss::filterAdmin($children);
}
},
],
];

View file

@ -44,7 +44,7 @@ interface ViewsHandlerInterface extends ViewsPluginInterface {
/**
* Check whether given user has access to this handler.
*
* @param AccountInterface $account
* @param \Drupal\Core\Session\AccountInterface $account
* The user account to check.
*
* @return bool

View file

@ -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.
*

View file

@ -107,7 +107,7 @@ abstract class AreaPluginBase extends HandlerBase {
* @return array
* In any case we need a valid Drupal render array to return.
*/
public abstract function render($empty = FALSE);
abstract public function render($empty = FALSE);
/**
* Does that area have nothing to show.

View file

@ -42,7 +42,7 @@ class HTTPStatusCode extends AreaPluginBase {
] + $options;
// Add the HTTP status code, so it's easier for people to find it.
array_walk($options, function($title, $code) use(&$options) {
array_walk($options, function ($title, $code) use (&$options) {
$options[$code] = $this->t('@code (@title)', ['@code' => $code, '@title' => $title]);
});

View file

@ -47,7 +47,7 @@ class Result extends AreaPluginBase {
'@page_count -- the total page count',
],
];
$list = drupal_render($item_list);
$list = \Drupal::service('renderer')->render($item_list);
$form['content'] = [
'#title' => $this->t('Display'),
'#type' => 'textarea',
@ -115,11 +115,13 @@ class Result extends AreaPluginBase {
// Send the output.
if (!empty($total) || !empty($this->options['empty'])) {
$output .= Xss::filterAdmin(str_replace(array_keys($replacements), array_values($replacements), $format));
// Return as render array.
return [
'#markup' => $output,
];
}
// Return as render array.
return [
'#markup' => $output,
];
return [];
}
}

View file

@ -118,7 +118,7 @@ class View extends AreaPluginBase {
// Check if the view is part of the parent views of this view
$search = "$view_name:$display_id";
if (in_array($search, $this->view->parent_views)) {
drupal_set_message(t("Recursion detected in view @view display @display.", ['@view' => $view_name, '@display' => $display_id]), 'error');
\Drupal::messenger()->addError(t("Recursion detected in view @view display @display.", ['@view' => $view_name, '@display' => $display_id]));
}
else {
if (!empty($this->options['inherit_arguments']) && !empty($this->view->args)) {

View file

@ -325,7 +325,8 @@ abstract class ArgumentPluginBase extends HandlerBase implements CacheableDepend
'#suffix' => '</div>',
'#type' => 'item',
// Even if the plugin has no options add the key to the form_state.
'#input' => TRUE, // trick it into checking input to make #process run
// trick it into checking input to make #process run.
'#input' => TRUE,
'#states' => [
'visible' => [
':input[name="options[specify_validation]"]' => ['checked' => TRUE],
@ -397,7 +398,6 @@ abstract class ArgumentPluginBase extends HandlerBase implements CacheableDepend
return $output;
}
public function validateOptionsForm(&$form, FormStateInterface $form_state) {
$option_values = &$form_state->getValue('options');
if (empty($option_values)) {
@ -498,12 +498,14 @@ abstract class ArgumentPluginBase extends HandlerBase implements CacheableDepend
'method' => 'defaultDefault',
'form method' => 'defaultArgumentForm',
'has default argument' => TRUE,
'default only' => TRUE, // this can only be used for missing argument, not validation failure
// This can only be used for missing argument, not validation failure.
'default only' => TRUE,
],
'not found' => [
'title' => $this->t('Hide view'),
'method' => 'defaultNotFound',
'hard fail' => TRUE, // This is a hard fail condition
// This is a hard fail condition.
'hard fail' => TRUE,
],
'summary' => [
'title' => $this->t('Display a summary'),
@ -547,7 +549,7 @@ abstract class ArgumentPluginBase extends HandlerBase implements CacheableDepend
'#type' => 'checkbox',
'#title' => $this->t('Skip default argument for view URL'),
'#default_value' => $this->options['default_argument_skip_url'],
'#description' => $this->t('Select whether to include this default argument when constructing the URL for this view. Skipping default arguments is useful e.g. in the case of feeds.')
'#description' => $this->t('Select whether to include this default argument when constructing the URL for this view. Skipping default arguments is useful e.g. in the case of feeds.'),
];
$form['default_argument_type'] = [
@ -637,7 +639,7 @@ abstract class ArgumentPluginBase extends HandlerBase implements CacheableDepend
'#default_value' => $this->options['summary']['number_of_records'],
'#options' => [
0 => $this->getSortName(),
1 => $this->t('Number of records')
1 => $this->t('Number of records'),
],
'#states' => [
'visible' => [
@ -669,7 +671,8 @@ abstract class ArgumentPluginBase extends HandlerBase implements CacheableDepend
'#suffix' => '</div>',
'#id' => 'edit-options-summary-options-' . $id,
'#type' => 'item',
'#input' => TRUE, // trick it into checking input to make #process run
// Trick it into checking input to make #process run.
'#input' => TRUE,
'#states' => [
'visible' => [
':input[name="options[default_action]"]' => ['value' => 'summary'],
@ -717,6 +720,7 @@ abstract class ArgumentPluginBase extends HandlerBase implements CacheableDepend
$info = $this->defaultActions($this->options['validate']['fail']);
return $this->defaultAction($info);
}
/**
* Default action: ignore.
*

View file

@ -24,7 +24,7 @@ class MonthDate extends Date {
*/
public function summaryName($data) {
$month = str_pad($data->{$this->name_alias}, 2, '0', STR_PAD_LEFT);
return format_date(strtotime("2005" . $month . "15" . " 00:00:00 UTC" ), 'custom', $this->format, 'UTC');
return format_date(strtotime("2005" . $month . "15" . " 00:00:00 UTC"), 'custom', $this->format, 'UTC');
}
/**

View file

@ -86,7 +86,7 @@ class NumericArgument extends ArgumentPluginBase {
/**
* Override for specific title lookups.
* @return array
* Returns all titles, if it's just one title it's an array with one entry.
* Returns all titles, if it's just one title it's an array with one entry.
*/
public function titleQuery() {
return $this->value;
@ -105,16 +105,16 @@ class NumericArgument extends ArgumentPluginBase {
}
$placeholder = $this->placeholder();
$null_check = empty($this->options['not']) ? '' : "OR $this->tableAlias.$this->realField IS NULL";
$null_check = empty($this->options['not']) ? '' : " OR $this->tableAlias.$this->realField IS NULL";
if (count($this->value) > 1) {
$operator = empty($this->options['not']) ? 'IN' : 'NOT IN';
$placeholder .= '[]';
$this->query->addWhereExpression(0, "$this->tableAlias.$this->realField $operator($placeholder) $null_check", [$placeholder => $this->value]);
$this->query->addWhereExpression(0, "$this->tableAlias.$this->realField $operator($placeholder)" . $null_check, [$placeholder => $this->value]);
}
else {
$operator = empty($this->options['not']) ? '=' : '!=';
$this->query->addWhereExpression(0, "$this->tableAlias.$this->realField $operator $placeholder $null_check", [$placeholder => $this->argument]);
$this->query->addWhereExpression(0, "$this->tableAlias.$this->realField $operator $placeholder" . $null_check, [$placeholder => $this->argument]);
}
}

View file

@ -2,7 +2,6 @@
namespace Drupal\views\Plugin\views\argument;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Database\Database;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\ContextDefinition;
@ -214,7 +213,7 @@ class StringArgument extends ArgumentPluginBase {
// converting the arguments to lowercase.
if ($this->options['case'] != 'none' && Database::getConnection()->databaseType() == 'pgsql') {
foreach ($this->value as $key => $value) {
$this->value[$key] = Unicode::strtolower($value);
$this->value[$key] = mb_strtolower($value);
}
}

View file

@ -42,7 +42,7 @@ abstract class ArgumentDefaultPluginBase extends PluginBase {
*
* This needs to be overridden by every default argument handler to properly do what is needed.
*/
public function getArgument() { }
public function getArgument() {}
/**
* Sets the parent argument this plugin is associated with.
@ -58,28 +58,32 @@ abstract class ArgumentDefaultPluginBase extends PluginBase {
* Retrieve the options when this is a new access
* control plugin
*/
protected function defineOptions() { return []; }
protected function defineOptions() {
return [];
}
/**
* Provide the default form for setting options.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) { }
public function buildOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Provide the default form form for validating options
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) { }
public function validateOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Provide the default form form for submitting options
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state, &$options = []) { }
public function submitOptionsForm(&$form, FormStateInterface $form_state, &$options = []) {}
/**
* Determine if the administrator has the privileges to use this
* plugin
*/
public function access() { return TRUE; }
public function access() {
return TRUE;
}
/**
* If we don't have access to the form but are showing it anyway, ensure that

View file

@ -53,27 +53,31 @@ abstract class ArgumentValidatorPluginBase extends PluginBase {
/**
* Retrieves the options when this is a new access control plugin.
*/
protected function defineOptions() { return []; }
protected function defineOptions() {
return [];
}
/**
* Provides the default form for setting options.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) { }
public function buildOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Provides the default form for validating options.
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) { }
public function validateOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Provides the default form for submitting options.
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state, &$options = []) { }
public function submitOptionsForm(&$form, FormStateInterface $form_state, &$options = []) {}
/**
* Determines if the administrator has the privileges to use this plugin.
*/
public function access() { return TRUE; }
public function access() {
return TRUE;
}
/**
* Blocks user input when the form is shown but we don´t have access.
@ -92,7 +96,9 @@ abstract class ArgumentValidatorPluginBase extends PluginBase {
/**
* Performs validation for a given argument.
*/
public function validateArgument($arg) { return TRUE; }
public function validateArgument($arg) {
return TRUE;
}
/**
* Processes the summary arguments for displaying.
@ -102,7 +108,7 @@ abstract class ArgumentValidatorPluginBase extends PluginBase {
* for a faster query. But there are use cases where you want to use
* the old value again, for example the summary.
*/
public function processSummaryArguments(&$args) { }
public function processSummaryArguments(&$args) {}
/**
* Returns a context definition for this argument.
@ -111,7 +117,7 @@ abstract class ArgumentValidatorPluginBase extends PluginBase {
* A context definition that represents the argument or NULL if that is
* not possible.
*/
public function getContextDefinition() { }
public function getContextDefinition() {}
}

View file

@ -5,7 +5,7 @@ namespace Drupal\views\Plugin\views\argument_validator;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Plugin\Context\EntityContextDefinition;
use Drupal\views\Plugin\views\argument\ArgumentPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -233,7 +233,9 @@ class Entity extends ArgumentValidatorPluginBase {
* {@inheritdoc}
*/
public function getContextDefinition() {
return new ContextDefinition('entity:' . $this->definition['entity_type'], $this->argument->adminLabel(), FALSE);
return EntityContextDefinition::fromEntityTypeId($this->definition['entity_type'])
->setLabel($this->argument->adminLabel())
->setRequired(FALSE);
}
}

View file

@ -97,6 +97,9 @@ abstract class CachePluginBase extends PluginBase {
* Save data to the cache.
*
* A plugin should override this to provide specialized caching behavior.
*
* @param $type
* The cache type, either 'query', 'result'.
*/
public function cacheSet($type) {
switch ($type) {
@ -119,6 +122,12 @@ abstract class CachePluginBase extends PluginBase {
* Retrieve data from the cache.
*
* A plugin should override this to provide specialized caching behavior.
*
* @param $type
* The cache type, either 'query', 'result'.
*
* @return bool
* TRUE if data has been taken from the cache, otherwise FALSE.
*/
public function cacheGet($type) {
$cutoff = $this->cacheExpire($type);
@ -154,24 +163,27 @@ abstract class CachePluginBase extends PluginBase {
/**
* Post process any rendered data.
*
* This can be valuable to be able to cache a view and still have some level of
* dynamic output. In an ideal world, the actual output will include HTML
* This can be valuable to be able to cache a view and still have some level
* of dynamic output. In an ideal world, the actual output will include HTML
* comment based tokens, and then the post process can replace those tokens.
*
* Example usage. If it is known that the view is a node view and that the
* primary field will be a nid, you can do something like this:
*
* <!--post-FIELD-NID-->
* @code
* <!--post-FIELD-NID-->
* @endcode
*
* And then in the post render, create an array with the text that should
* go there:
*
* strtr($output, array('<!--post-FIELD-1-->', 'output for FIELD of nid 1');
* @code
* strtr($output, array('<!--post-FIELD-1-->', 'output for FIELD of nid 1');
* @endcode
*
* All of the cached result data will be available in $view->result, as well,
* so all ids used in the query should be discoverable.
*/
public function postRender(&$output) { }
public function postRender(&$output) {}
/**
* Calculates and sets a cache ID used for the result cache.
@ -190,7 +202,7 @@ abstract class CachePluginBase extends PluginBase {
$query = clone $build_info[$index];
$query->preExecute();
$build_info[$index] = [
'query' => (string)$query,
'query' => (string) $query,
'arguments' => $query->getArguments(),
];
}
@ -292,7 +304,7 @@ abstract class CachePluginBase extends PluginBase {
/**
* Returns the row cache tags.
*
* @param ResultRow $row
* @param \Drupal\views\ResultRow $row
* A result row.
*
* @return string[]

View file

@ -19,7 +19,6 @@ class None extends CachePluginBase {
return $this->t('None');
}
/**
* Overrides \Drupal\views\Plugin\views\cache\CachePluginBase::cacheGet().
*

View file

@ -2,6 +2,8 @@
namespace Drupal\views\Plugin\views\display;
use Drupal\Component\Plugin\Discovery\CachedDiscoveryInterface;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\Block\ViewsBlock;
@ -42,6 +44,13 @@ class Block extends DisplayPluginBase {
*/
protected $entityManager;
/**
* The block manager.
*
* @var \Drupal\Core\Block\BlockManagerInterface
*/
protected $blockManager;
/**
* Constructs a new Block instance.
*
@ -53,11 +62,14 @@ class Block extends DisplayPluginBase {
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
* The block manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, BlockManagerInterface $block_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->blockManager = $block_manager;
}
/**
@ -68,7 +80,8 @@ class Block extends DisplayPluginBase {
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')
$container->get('entity.manager'),
$container->get('plugin.manager.block')
);
}
@ -364,6 +377,9 @@ class Block extends DisplayPluginBase {
$block->delete();
}
}
if ($this->blockManager instanceof CachedDiscoveryInterface) {
$this->blockManager->clearCachedDefinitions();
}
}
}

View file

@ -28,7 +28,9 @@ class DefaultDisplay extends DisplayPluginBase {
* Determine if this display is the 'default' display which contains
* fallback settings
*/
public function isDefaultDisplay() { return TRUE; }
public function isDefaultDisplay() {
return TRUE;
}
/**
* The default execute handler fully renders the view.

View file

@ -4,7 +4,7 @@ namespace Drupal\views\Plugin\views\display;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\CacheableDependencyInterface;
@ -164,7 +164,6 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
}
}
$this->setOptionDefaults($this->options, $this->defineOptions());
$this->display = &$display;
@ -234,7 +233,9 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
/**
* {@inheritdoc}
*/
public function isDefaultDisplay() { return FALSE; }
public function isDefaultDisplay() {
return FALSE;
}
/**
* {@inheritdoc}
@ -394,7 +395,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
/**
* {@inheritdoc}
*/
public function attachTo(ViewExecutable $view, $display_id, array &$build) { }
public function attachTo(ViewExecutable $view, $display_id, array &$build) {}
/**
* {@inheritdoc}
@ -665,17 +666,23 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
/**
* {@inheritdoc}
*/
public function hasPath() { return FALSE; }
public function hasPath() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function usesLinkDisplay() { return !$this->hasPath(); }
public function usesLinkDisplay() {
return !$this->hasPath();
}
/**
* {@inheritdoc}
*/
public function usesExposedFormInBlock() { return $this->hasPath(); }
public function usesExposedFormInBlock() {
return $this->hasPath();
}
/**
* {@inheritdoc}
@ -772,7 +779,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
return $this->default_display->getOption($option);
}
if (array_key_exists($option, $this->options)) {
if (isset($this->options[$option]) || array_key_exists($option, $this->options)) {
return $this->options[$option];
}
}
@ -955,7 +962,6 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
return $this->dependencies;
}
/**
* {@inheritdoc}
*/
@ -1015,7 +1021,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
}
if (!empty($class)) {
$text = SafeMarkup::format('<span>@text</span>', ['@text' => $text]);
$text = new FormattableMarkup('<span>@text</span>', ['@text' => $text]);
}
if (empty($title)) {
@ -1026,13 +1032,13 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
'js' => 'nojs',
'view' => $this->view->storage->id(),
'display_id' => $this->display['id'],
'type' => $section
'type' => $section,
], [
'attributes' => [
'class' => ['views-ajax-link', $class],
'title' => $title,
'id' => Html::getUniqueId('views-' . $this->display['id'] . '-' . $section)
]
'id' => Html::getUniqueId('views-' . $this->display['id'] . '-' . $section),
],
]));
}
@ -1463,7 +1469,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
'#title' => $this->t('Show contextual links'),
'#default_value' => $this->getOption('show_admin_links'),
];
break;
break;
case 'use_more':
$form['#title'] .= $this->t('Add a more link to the bottom of the display.');
$form['use_more'] = [
@ -1858,7 +1864,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
if (preg_match('/[^a-zA-Z0-9-_ ]/', $css_class)) {
$form_state->setError($form['css_class'], $this->t('CSS classes must be alphanumeric or dashes only.'));
}
break;
break;
case 'display_id':
if ($form_state->getValue('display_id')) {
if (preg_match('/[^a-z0-9_]/', $form_state->getValue('display_id'))) {
@ -2051,7 +2057,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
/**
* {@inheritdoc}
*/
public function renderFilters() { }
public function renderFilters() {}
/**
* {@inheritdoc}
@ -2115,7 +2121,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
'#cache' => &$this->view->element['#cache'],
];
$this->applyDisplayCachablityMetadata($this->view->element);
$this->applyDisplayCacheabilityMetadata($this->view->element);
return $element;
}
@ -2126,7 +2132,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
* @param array $element
* The render array with updated cacheability metadata.
*/
protected function applyDisplayCachablityMetadata(array &$element) {
protected function applyDisplayCacheabilityMetadata(array &$element) {
/** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache */
$cache = $this->getPlugin('cache');
@ -2138,6 +2144,22 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
->applyTo($element);
}
/**
* Applies the cacheability of the current display to the given render array.
*
* @param array $element
* The render array with updated cacheability metadata.
*
* @deprecated in Drupal 8.4.0, will be removed before Drupal 9.0. Use
* DisplayPluginBase::applyDisplayCacheabilityMetadata instead.
*
* @see \Drupal\views\Plugin\views\display\DisplayPluginBase::applyDisplayCacheabilityMetadata()
*/
protected function applyDisplayCachablityMetadata(array &$element) {
@trigger_error('The DisplayPluginBase::applyDisplayCachablityMetadata method is deprecated since version 8.4 and will be removed in 9.0. Use DisplayPluginBase::applyDisplayCacheabilityMetadata instead.', E_USER_DEPRECATED);
$this->applyDisplayCacheabilityMetadata($element);
}
/**
* {@inheritdoc}
*/
@ -2309,7 +2331,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
/**
* {@inheritdoc}
*/
public function execute() { }
public function execute() {}
/**
* {@inheritdoc}
@ -2330,7 +2352,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
// of cacheability metadata (e.g.: cache contexts), so they can bubble up.
// Thus, we add the cacheability metadata first, then modify / remove the
// cache keys depending on the $cache argument.
$this->applyDisplayCachablityMetadata($this->view->element);
$this->applyDisplayCacheabilityMetadata($this->view->element);
if ($cache) {
$this->view->element['#cache'] += ['keys' => []];
// Places like \Drupal\views\ViewExecutable::setCurrentPage() set up an
@ -2564,7 +2586,7 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
public function getPagerText() {
return [
'items per page title' => $this->t('Items to display'),
'items per page description' => $this->t('Enter 0 for no limit.')
'items per page description' => $this->t('Enter 0 for no limit.'),
];
}

View file

@ -457,7 +457,6 @@ interface DisplayPluginInterface {
*/
public function execute();
/**
* Builds a basic render array which can be properly render cached.
*

View file

@ -20,6 +20,11 @@ namespace Drupal\views\Plugin\views\display;
*/
class Embed extends DisplayPluginBase {
/**
* {@inheritdoc}
*/
protected $usesAttachments = TRUE;
/**
* {@inheritdoc}
*/

View file

@ -2,6 +2,8 @@
namespace Drupal\views\Plugin\views\display;
use Drupal\Core\Database\Query\Condition;
/**
* The plugin that handles an EntityReference display.
*
@ -52,9 +54,6 @@ class EntityReference extends DisplayPluginBase {
$options['row']['contains']['type'] = ['default' => 'entity_reference'];
$options['defaults']['default']['row'] = FALSE;
// Make sure the query is not cached.
$options['defaults']['default']['cache'] = FALSE;
// Set the display title to an empty string (not used in this display type).
$options['title']['default'] = '';
$options['defaults']['default']['title'] = FALSE;
@ -63,13 +62,12 @@ class EntityReference extends DisplayPluginBase {
}
/**
* Overrides \Drupal\views\Plugin\views\display\DisplayPluginBase::optionsSummary().
*
* Disable 'cache' and 'title' so it won't be changed.
* {@inheritdoc}
*/
public function optionsSummary(&$categories, &$options) {
parent::optionsSummary($categories, $options);
unset($options['query']);
// Disable 'title' so it won't be changed from the default set in
// \Drupal\views\Plugin\views\display\EntityReference::defineOptions.
unset($options['title']);
}
@ -88,13 +86,16 @@ class EntityReference extends DisplayPluginBase {
}
/**
* {@inheritdoc}
* Builds the view result as a renderable array.
*
* @return array
* Renderable array or empty array.
*/
public function render() {
if (!empty($this->view->result) && $this->view->style_plugin->evenEmpty()) {
return $this->view->style_plugin->render($this->view->result);
}
return '';
return [];
}
/**
@ -131,7 +132,7 @@ class EntityReference extends DisplayPluginBase {
}
// Multiple search fields are OR'd together.
$conditions = db_or();
$conditions = new Condition('OR');
// Build the condition using the selected search fields.
foreach ($style_options['options']['search_fields'] as $field_id) {

View file

@ -73,7 +73,6 @@ class Feed extends PathPluginBase implements ResponseDisplayPluginInterface {
return $response;
}
/**
* {@inheritdoc}
*/
@ -106,7 +105,7 @@ class Feed extends PathPluginBase implements ResponseDisplayPluginInterface {
public function render() {
$build = $this->view->style_plugin->render($this->view->result);
$this->applyDisplayCachablityMetadata($build);
$this->applyDisplayCacheabilityMetadata($build);
return $build;
}
@ -141,7 +140,7 @@ class Feed extends PathPluginBase implements ResponseDisplayPluginInterface {
// Overrides for standard stuff.
$options['style']['contains']['type']['default'] = 'rss';
$options['style']['contains']['options']['default'] = ['description' => ''];
$options['style']['contains']['options']['default'] = ['description' => ''];
$options['sitename_title']['default'] = FALSE;
$options['row']['contains']['type']['default'] = 'rss_fields';
$options['defaults']['default']['style'] = FALSE;

View file

@ -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.
*
@ -247,7 +259,7 @@ class Page extends PathPluginBase {
'none' => $this->t('No menu entry'),
'normal' => $this->t('Normal menu entry'),
'tab' => $this->t('Menu tab'),
'default tab' => $this->t('Default menu tab')
'default tab' => $this->t('Default menu tab'),
],
'#default_value' => $menu['type'],
];
@ -520,7 +532,7 @@ class Page extends PathPluginBase {
public function getPagerText() {
return [
'items per page title' => $this->t('Items per page'),
'items per page description' => $this->t('Enter 0 for no limit.')
'items per page description' => $this->t('Enter 0 for no limit.'),
];
}

View file

@ -36,45 +36,45 @@ abstract class DisplayExtenderPluginBase extends PluginBase {
/**
* Provide a form to edit options for this plugin.
*/
public function defineOptionsAlter(&$options) { }
public function defineOptionsAlter(&$options) {}
/**
* Provide a form to edit options for this plugin.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) { }
public function buildOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Validate the options form.
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) { }
public function validateOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Handle any special handling on the validate form.
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) { }
public function submitOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Set up any variables on the view prior to execution.
*/
public function preExecute() { }
public function preExecute() {}
/**
* Inject anything into the query that the display_extender handler needs.
*/
public function query() { }
public function query() {}
/**
* Provide the default summary for options in the views UI.
*
* This output is returned as an array.
*/
public function optionsSummary(&$categories, &$options) { }
public function optionsSummary(&$categories, &$options) {}
/**
* Static member function to list which sections are defaultable
* and what items each section contains.
*/
public function defaultableSections(&$sections, $section = NULL) { }
public function defaultableSections(&$sections, $section = NULL) {}
}

View file

@ -179,22 +179,22 @@ abstract class ExposedFormPluginBase extends PluginBase implements CacheableDepe
/**
* {@inheritdoc}
*/
public function preRender($values) { }
public function preRender($values) {}
/**
* {@inheritdoc}
*/
public function postRender(&$output) { }
public function postRender(&$output) {}
/**
* {@inheritdoc}
*/
public function preExecute() { }
public function preExecute() {}
/**
* {@inheritdoc}
*/
public function postExecute() { }
public function postExecute() {}
/**
* {@inheritdoc}

View file

@ -0,0 +1,512 @@
<?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\Messenger\MessengerInterface;
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;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* 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.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, LanguageManagerInterface $language_manager, MessengerInterface $messenger) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->actionStorage = $entity_manager->getStorage('action');
$this->languageManager = $language_manager;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager'),
$container->get('language_manager'),
$container->get('messenger')
);
}
/**
* {@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->messenger->addError($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(),
]));
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) {
$this->messenger->addStatus($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;
}
/**
* 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;
}
}

View file

@ -119,7 +119,7 @@ class Date extends FieldPluginBase {
'#type' => 'select',
'#title' => $this->t('Timezone'),
'#description' => $this->t('Timezone to be used for date output.'),
'#options' => ['' => $this->t('- Default site/user timezone -')] + system_time_zones(FALSE),
'#options' => ['' => $this->t('- Default site/user timezone -')] + system_time_zones(FALSE, TRUE),
'#default_value' => $this->options['timezone'],
];
foreach (array_merge(['custom'], array_keys($date_formats)) as $timezone_date_formats) {
@ -143,7 +143,9 @@ class Date extends FieldPluginBase {
if ($value) {
$timezone = !empty($this->options['timezone']) ? $this->options['timezone'] : NULL;
$time_diff = REQUEST_TIME - $value; // will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence)
// Will be positive for a datetime in the past (ago), and negative for a
// datetime in the future (hence).
$time_diff = REQUEST_TIME - $value;
switch ($format) {
case 'raw time ago':
return $this->dateFormatter->formatTimeDiffSince($value, ['granularity' => is_numeric($custom_format) ? $custom_format : 2]);

View file

@ -23,6 +23,7 @@ use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\views\FieldAPIHandlerTrait;
use Drupal\views\Entity\Render\EntityFieldRenderer;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\DependentWithRemovalPluginInterface;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -34,7 +35,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*
* @ViewsField("field")
*/
class EntityField extends FieldPluginBase implements CacheableDependencyInterface, MultiItemsFieldHandlerInterface {
class EntityField extends FieldPluginBase implements CacheableDependencyInterface, MultiItemsFieldHandlerInterface, DependentWithRemovalPluginInterface {
use FieldAPIHandlerTrait;
use PluginDependencyTrait;
@ -315,24 +316,33 @@ class EntityField extends FieldPluginBase implements CacheableDependencyInterfac
}
/**
* Gets the field storage of the used field.
* Gets the field storage definition.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface
* The field storage definition used by this handler.
*/
protected function getFieldStorageDefinition() {
$entity_type_id = $this->definition['entity_type'];
$field_storage_definitions = $this->entityManager->getFieldStorageDefinitions($entity_type_id);
$field_storage = NULL;
// @todo Unify 'entity field'/'field_name' instead of converting back and
// forth. https://www.drupal.org/node/2410779
if (isset($this->definition['field_name'])) {
$field_storage = $field_storage_definitions[$this->definition['field_name']];
if (isset($this->definition['field_name']) && isset($field_storage_definitions[$this->definition['field_name']])) {
return $field_storage_definitions[$this->definition['field_name']];
}
elseif (isset($this->definition['entity field'])) {
$field_storage = $field_storage_definitions[$this->definition['entity field']];
if (isset($this->definition['entity field']) && isset($field_storage_definitions[$this->definition['entity field']])) {
return $field_storage_definitions[$this->definition['entity field']];
}
// The list of field storage definitions above does not include computed
// base fields, so we need to explicitly fetch a list of all base fields in
// order to support them.
// @see \Drupal\Core\Entity\EntityFieldManager::getFieldStorageDefinitions()
$base_fields = $this->entityManager->getBaseFieldDefinitions($entity_type_id);
if (isset($this->definition['field_name']) && isset($base_fields[$this->definition['field_name']])) {
return $base_fields[$this->definition['field_name']]->getFieldStorageDefinition();
}
return $field_storage;
}
/**
@ -398,10 +408,10 @@ class EntityField extends FieldPluginBase implements CacheableDependencyInterfac
];
$options['multi_type'] = [
'default' => 'separator'
'default' => 'separator',
];
$options['separator'] = [
'default' => ', '
'default' => ', ',
];
$options['field_api_classes'] = [
@ -1042,10 +1052,19 @@ class EntityField extends FieldPluginBase implements CacheableDependencyInterfac
*/
public function getValue(ResultRow $values, $field = NULL) {
$entity = $this->getEntity($values);
// Ensure the object is not NULL before attempting to translate it.
if ($entity === NULL) {
return NULL;
}
// Retrieve the translated object.
$translated_entity = $this->getEntityFieldRenderer()->getEntityTranslation($entity, $values);
// Some bundles might not have a specific field, in which case the entity
// (potentially a fake one) doesn't have it either.
/** @var \Drupal\Core\Field\FieldItemListInterface $field_item_list */
$field_item_list = isset($entity->{$this->definition['field_name']}) ? $entity->{$this->definition['field_name']} : NULL;
$field_item_list = isset($translated_entity->{$this->definition['field_name']}) ? $translated_entity->{$this->definition['field_name']} : NULL;
if (!isset($field_item_list)) {
// There isn't anything we can do without a valid field.
@ -1077,4 +1096,29 @@ class EntityField extends FieldPluginBase implements CacheableDependencyInterfac
}
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
// See if this handler is responsible for any of the dependencies being
// removed. If this is the case, indicate that this handler needs to be
// removed from the View.
$remove = FALSE;
// Get all the current dependencies for this handler.
$current_dependencies = $this->calculateDependencies();
foreach ($current_dependencies as $group => $dependency_list) {
// Check if any of the handler dependencies match the dependencies being
// removed.
foreach ($dependency_list as $config_key) {
if (isset($dependencies[$group]) && array_key_exists($config_key, $dependencies[$group])) {
// This handlers dependency matches a dependency being removed,
// indicate that this handler needs to be removed.
$remove = TRUE;
break 2;
}
}
}
return $remove;
}
}

View file

@ -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];
}
}

View file

@ -46,7 +46,7 @@ class EntityOperations extends FieldPluginBase {
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* The entity manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/
@ -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'],
];
}
@ -158,6 +158,7 @@ class EntityOperations extends FieldPluginBase {
protected function getLanguageManager() {
return $this->languageManager;
}
/**
* {@inheritdoc}
*/

View file

@ -4,7 +4,6 @@ namespace Drupal\views\Plugin\views\field;
use Drupal\Component\Utility\Html;
use Drupal\Component\Render\MarkupInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
@ -68,6 +67,9 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
*/
const RENDER_TEXT_PHASE_EMPTY = 2;
/**
* @var string
*/
public $field_alias = 'unknown';
public $aliases = [];
@ -104,7 +106,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
/**
* Keeps track of the last render index.
*
* @var int|NULL
* @var int|null
*/
protected $lastRenderIndex;
@ -183,7 +185,13 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
}
if (empty($table_alias)) {
debug(t('Handler @handler tried to add additional_field @identifier but @table could not be added!', ['@handler' => $this->definition['id'], '@identifier' => $identifier, '@table' => $info['table']]));
trigger_error(sprintf(
"Handler % tried to add additional_field %s but % could not be added!",
$this->definition['id'],
$identifier,
$info['table']
), E_USER_WARNING);
$this->aliases[$identifier] = 'broken';
continue;
}
@ -309,7 +317,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
// @todo Add possible html5 elements.
$elements = [
'' => $this->t('- Use default -'),
'0' => $this->t('- None -')
'0' => $this->t('- None -'),
];
$elements += \Drupal::config('views.settings')->get('field_rewrite_elements');
}
@ -321,9 +329,9 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
* {@inheritdoc}
*/
public function elementClasses($row_index = NULL) {
$classes = explode(' ', $this->options['element_class']);
$classes = $this->tokenizeValue($this->options['element_class'], $row_index);
$classes = explode(' ', $classes);
foreach ($classes as &$class) {
$class = $this->tokenizeValue($class, $row_index);
$class = Html::cleanCssIdentifier($class);
}
return implode(' ', $classes);
@ -368,9 +376,9 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
* {@inheritdoc}
*/
public function elementLabelClasses($row_index = NULL) {
$classes = explode(' ', $this->options['element_label_class']);
$classes = $this->tokenizeValue($this->options['element_label_class'], $row_index);
$classes = explode(' ', $classes);
foreach ($classes as &$class) {
$class = $this->tokenizeValue($class, $row_index);
$class = Html::cleanCssIdentifier($class);
}
return implode(' ', $classes);
@ -380,9 +388,9 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
* {@inheritdoc}
*/
public function elementWrapperClasses($row_index = NULL) {
$classes = explode(' ', $this->options['element_wrapper_class']);
$classes = $this->tokenizeValue($this->options['element_wrapper_class'], $row_index);
$classes = explode(' ', $classes);
foreach ($classes as &$class) {
$class = $this->tokenizeValue($class, $row_index);
$class = Html::cleanCssIdentifier($class);
}
return implode(' ', $classes);
@ -857,7 +865,6 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
],
];
// Get a list of the available fields and arguments for token replacement.
// Setup the tokens for fields.
@ -865,10 +872,10 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
$optgroup_arguments = (string) t('Arguments');
$optgroup_fields = (string) t('Fields');
foreach ($previous as $id => $label) {
$options[$optgroup_fields]["{{ $id }}"] = substr(strrchr($label, ":"), 2 );
$options[$optgroup_fields]["{{ $id }}"] = substr(strrchr($label, ":"), 2);
}
// Add the field to the list of options.
$options[$optgroup_fields]["{{ {$this->options['id']} }}"] = substr(strrchr($this->adminLabel(), ":"), 2 );
$options[$optgroup_fields]["{{ {$this->options['id']} }}"] = substr(strrchr($this->adminLabel(), ":"), 2);
foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
$options[$optgroup_arguments]["{{ arguments.$arg }}"] = $this->t('@argument title', ['@argument' => $handler->adminLabel()]);
@ -1054,7 +1061,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',
];
@ -1105,7 +1112,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
/**
* {@inheritdoc}
*/
public function preRender(&$values) { }
public function preRender(&$values) {}
/**
* {@inheritdoc}
@ -1286,7 +1293,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
$base_path = base_path();
// Checks whether the path starts with the base_path.
if (strpos($more_link_path, $base_path) === 0) {
$more_link_path = Unicode::substr($more_link_path, Unicode::strlen($base_path));
$more_link_path = mb_substr($more_link_path, mb_strlen($base_path));
}
// @todo Views should expect and store a leading /. See
@ -1386,7 +1393,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
];
$alter += [
'path' => NULL
'path' => NULL,
];
$path = $alter['path'];
@ -1713,14 +1720,14 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
* field ID is terms, then the tokens might be {{ terms__tid }} and
* {{ terms__name }}.
*/
protected function addSelfTokens(&$tokens, $item) { }
protected function addSelfTokens(&$tokens, $item) {}
/**
* Document any special tokens this field might use for itself.
*
* @see addSelfTokens()
*/
protected function documentSelfTokens(&$tokens) { }
protected function documentSelfTokens(&$tokens) {}
/**
* {@inheritdoc}
@ -1790,8 +1797,8 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
* The trimmed string.
*/
public static function trimText($alter, $value) {
if (Unicode::strlen($value) > $alter['max_length']) {
$value = Unicode::substr($value, 0, $alter['max_length']);
if (mb_strlen($value) > $alter['max_length']) {
$value = mb_substr($value, 0, $alter['max_length']);
if (!empty($alter['word_boundary'])) {
$regex = "(.*)\b.+";
if (function_exists('mb_ereg')) {

View file

@ -18,7 +18,9 @@ use Drupal\views\ResultRow;
class MachineName extends FieldPluginBase {
/**
* @var array Stores the available options.
* Stores the available options.
*
* @var array
*/
protected $valueOptions;

View file

@ -5,7 +5,7 @@ namespace Drupal\views\Plugin\views\field;
use Drupal\views\ResultRow;
/**
* Defines a field hander which renders multiple items per row.
* Defines a field handler which renders multiple items per row.
*/
interface MultiItemsFieldHandlerInterface extends FieldHandlerInterface {

View file

@ -78,8 +78,8 @@ abstract class PrerenderList extends FieldPluginBase implements MultiItemsFieldH
'#template' => '{{ items|safe_join(separator) }}',
'#context' => [
'items' => $items,
'separator' => $this->sanitizeValue($this->options['separator'], 'xss_admin')
]
'separator' => $this->sanitizeValue($this->options['separator'], 'xss_admin'),
],
];
}
else {
@ -90,7 +90,7 @@ abstract class PrerenderList extends FieldPluginBase implements MultiItemsFieldH
'#list_type' => $this->options['type'],
];
}
return drupal_render($render);
return \Drupal::service('renderer')->render($render);
}
}

View file

@ -46,7 +46,7 @@ class RenderedEntity extends FieldPluginBase implements CacheableDependencyInter
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* The entity manager.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/

View file

@ -48,8 +48,6 @@ class BooleanOperator extends FilterPluginBase {
// Whether to accept NULL as a false value or not
public $accept_null = FALSE;
/**
* {@inheritdoc}
*/

View file

@ -56,6 +56,8 @@ class Bundle extends InOperator {
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info_service
* The bundle info service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, EntityTypeBundleInfoInterface $bundle_info_service) {
parent::__construct($configuration, $plugin_id, $plugin_definition);

View file

@ -166,8 +166,10 @@ class Date extends NumericFilter {
$b = intval(strtotime($this->value['max'], 0));
if ($this->value['type'] == 'offset') {
$a = '***CURRENT_TIME***' . sprintf('%+d', $a); // keep sign
$b = '***CURRENT_TIME***' . sprintf('%+d', $b); // keep sign
// Keep sign.
$a = '***CURRENT_TIME***' . sprintf('%+d', $a);
// Keep sign.
$b = '***CURRENT_TIME***' . sprintf('%+d', $b);
}
// This is safe because we are manually scrubbing the values.
// It is necessary to do it this way because $a and $b are formulas when using an offset.
@ -178,7 +180,8 @@ class Date extends NumericFilter {
protected function opSimple($field) {
$value = intval(strtotime($this->value['value'], 0));
if (!empty($this->value['type']) && $this->value['type'] == 'offset') {
$value = '***CURRENT_TIME***' . sprintf('%+d', $value); // keep sign
// Keep sign.
$value = '***CURRENT_TIME***' . sprintf('%+d', $value);
}
// This is safe because we are manually scrubbing the value.
// It is necessary to do it this way because $value is a formula when using an offset.

View file

@ -53,6 +53,8 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
/**
* Contains the operator which is used on the query.
*
* @var string
*/
public $operator = '=';
@ -104,7 +106,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
$this->options['expose']['multiple'] = TRUE;
}
// If there are relationships in the view, allow empty should be true
// so that we can do IS NULL checks on items. Not all filters respect
// allow empty, but string and numeric do and that covers enough.
@ -131,9 +132,11 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
'required' => ['default' => FALSE],
'remember' => ['default' => FALSE],
'multiple' => ['default' => FALSE],
'remember_roles' => ['default' => [
RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID,
]],
'remember_roles' => [
'default' => [
RoleInterface::AUTHENTICATED_ID => RoleInterface::AUTHENTICATED_ID,
],
],
],
];
@ -174,7 +177,9 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
/**
* Determine if a filter can be exposed.
*/
public function canExpose() { return TRUE; }
public function canExpose() {
return TRUE;
}
/**
* Determine if a filter can be converted into a group.
@ -302,18 +307,20 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
* Provide a list of options for the default operator form.
* Should be overridden by classes that don't override operatorForm
*/
public function operatorOptions() { return []; }
public function operatorOptions() {
return [];
}
/**
* Validate the operator form.
*/
protected function operatorValidate($form, FormStateInterface $form_state) { }
protected function operatorValidate($form, FormStateInterface $form_state) {}
/**
* Perform any necessary changes to the form values prior to storage.
* There is no need for this function to actually store the data.
*/
public function operatorSubmit($form, FormStateInterface $form_state) { }
public function operatorSubmit($form, FormStateInterface $form_state) {}
/**
* Shortcut to display the value form.
@ -341,13 +348,13 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
/**
* Validate the options form.
*/
protected function valueValidate($form, FormStateInterface $form_state) { }
protected function valueValidate($form, FormStateInterface $form_state) {}
/**
* Perform any necessary changes to the form values prior to storage.
* There is no need for this function to actually store the data.
*/
protected function valueSubmit($form, FormStateInterface $form_state) { }
protected function valueSubmit($form, FormStateInterface $form_state) {}
/**
* Shortcut to display the exposed options form.
@ -649,7 +656,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
return FALSE;
}
/**
* Validate the build group options form.
*/
@ -828,7 +834,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
}
}
/**
* Render our chunk of the exposed filter form when selecting
*
@ -989,7 +994,9 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
'#default_value' => $this->options['group_info']['remember'],
];
$groups = ['All' => $this->t('- Any -')]; // The string '- Any -' will not be rendered see @theme_views_ui_build_group_filter_form
// The string '- Any -' will not be rendered.
// @see theme_views_ui_build_group_filter_form()
$groups = ['All' => $this->t('- Any -')];
// Provide 3 options to start when we are in a new group.
if (count($this->options['group_info']['group_items']) == 0) {
@ -1096,7 +1103,7 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
'#required' => TRUE,
'#attributes' => [
'class' => ['default-radios'],
]
],
];
// From all groups, let chose which is the default.
$form['group_info']['default_group_multiple'] = [
@ -1105,7 +1112,7 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
'#default_value' => $this->options['group_info']['default_group_multiple'],
'#attributes' => [
'class' => ['default-checkboxes'],
]
],
];
$form['group_info']['add_group'] = [
@ -1156,7 +1163,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
$form_state->get('force_build_group_options', TRUE);
}
/**
* Make some translations to a form item to make it more suitable to
* exposing.
@ -1198,8 +1204,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
}
}
/**
* Sanitizes the HTML select element's options.
*

View file

@ -20,6 +20,7 @@ class GroupByNumeric extends NumericFilter {
$this->{$info[$this->operator]['method']}($field);
}
}
protected function opBetween($field) {
$placeholder_min = $this->placeholder();
$placeholder_max = $this->placeholder();
@ -52,6 +53,8 @@ class GroupByNumeric extends NumericFilter {
return $this->getField(parent::adminLabel($short));
}
public function canGroup() { return FALSE; }
public function canGroup() {
return FALSE;
}
}

View file

@ -88,7 +88,8 @@ class InOperator extends FilterPluginBase {
'#type' => 'checkbox',
'#title' => $this->t('Limit list to selected items'),
'#description' => $this->t('If checked, the only items presented to the user will be the ones selected here.'),
'#default_value' => !empty($this->options['expose']['reduce']), // safety
// Safety.
'#default_value' => !empty($this->options['expose']['reduce']),
];
}
@ -228,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])) {
@ -366,7 +369,7 @@ class InOperator extends FilterPluginBase {
if ($values !== '') {
$values .= ', ';
}
if (Unicode::strlen($values) > 8) {
if (mb_strlen($values) > 8) {
$values = Unicode::truncate($values, 8, FALSE, TRUE);
break;
}

View file

@ -0,0 +1,113 @@
<?php
namespace Drupal\views\Plugin\views\filter;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\views\Plugin\ViewsHandlerManager;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Filter to show only the latest revision of an entity.
*
* @ingroup views_filter_handlers
*
* @ViewsFilter("latest_revision")
*/
class LatestRevision extends FilterPluginBase implements ContainerFactoryPluginInterface {
/**
* Entity Type Manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Views Handler Plugin Manager.
*
* @var \Drupal\views\Plugin\ViewsHandlerManager
*/
protected $joinHandler;
/**
* Constructs a new LatestRevision.
*
* @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\EntityTypeManagerInterface $entity_type_manager
* Entity Type Manager Service.
* @param \Drupal\views\Plugin\ViewsHandlerManager $join_handler
* Views Handler Plugin Manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, ViewsHandlerManager $join_handler) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
$this->joinHandler = $join_handler;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration, $plugin_id, $plugin_definition,
$container->get('entity_type.manager'),
$container->get('plugin.manager.views.join')
);
}
/**
* {@inheritdoc}
*/
public function adminSummary() {
}
/**
* {@inheritdoc}
*/
protected function operatorForm(&$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function canExpose() {
return FALSE;
}
/**
* {@inheritdoc}
*/
public function query() {
/** @var \Drupal\views\Plugin\views\query\Sql $query */
$query = $this->query;
$query_base_table = $this->relationship ?: $this->view->storage->get('base_table');
$entity_type = $this->entityTypeManager->getDefinition($this->getEntityType());
$keys = $entity_type->getKeys();
$definition = [
'table' => $query_base_table,
'type' => 'LEFT',
'field' => $keys['id'],
'left_table' => $query_base_table,
'left_field' => $keys['id'],
'extra' => [
['left_field' => $keys['revision'], 'field' => $keys['revision'], 'operator' => '>'],
],
];
$join = $this->joinHandler->createInstance('standard', $definition);
$join_table_alias = $query->addTable($query_base_table, $this->relationship, $join);
$query->addWhere($this->options['group'], "$join_table_alias.{$keys['id']}", NULL, 'IS NULL');
}
}

View file

@ -103,6 +103,7 @@ class ManyToOne extends InOperator {
}
protected $valueFormType = 'select';
protected function valueForm(&$form, FormStateInterface $form_state) {
parent::valueForm($form, $form_state);
@ -129,6 +130,9 @@ class ManyToOne extends InOperator {
if (empty($this->value)) {
return;
}
// Form API returns unchecked options in the form of option_id => 0. This
// breaks the generated query for "is all of" filters so we remove them.
$this->value = array_filter($this->value, 'static::arrayFilterZero');
$this->helper->addFilter();
}

View file

@ -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 = [
'<' => [
@ -130,6 +190,7 @@ class NumericFilter extends FilterPluginBase {
return $options;
}
/**
* Provide a simple textfield for equality
*/
@ -165,6 +226,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 +249,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 +264,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.
@ -227,7 +300,7 @@ class NumericFilter extends FilterPluginBase {
// Ensure there is something in the 'value'.
$form['value'] = [
'#type' => 'value',
'#value' => NULL
'#value' => NULL,
];
}
}

View file

@ -2,6 +2,7 @@
namespace Drupal\views\Plugin\views\filter;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Form\FormStateInterface;
/**
@ -26,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
@ -210,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;
@ -230,7 +257,7 @@ class StringFilter extends FilterPluginBase {
// Ensure there is something in the 'value'.
$form['value'] = [
'#type' => 'value',
'#value' => NULL
'#value' => NULL,
];
}
}
@ -265,7 +292,7 @@ class StringFilter extends FilterPluginBase {
}
protected function opContainsWord($field) {
$where = $this->operator == 'word' ? db_or() : db_and();
$where = $this->operator == 'word' ? new Condition('OR') : new Condition('AND');
// Don't filter on empty strings.
if (empty($this->value)) {

View file

@ -0,0 +1,106 @@
<?php
namespace Drupal\views\Plugin\views\join;
use Drupal\Core\Database\Query\SelectInterface;
/**
* Implementation for the "field OR language" join.
*
* If the extra conditions contain either ".langcode" or ".bundle", they will be
* grouped and joined with OR instead of AND. The entire group will then be
* joined to the other conditions with AND.
*
* This is needed for configurable fields that are translatable on some bundles
* and untranslatable on others. The correct field values to fetch in this case
* have a langcode that matches the entity record *or* have a bundle on which
* the field is untranslatable. Thus, the entity base table (or data table, or
* revision data table, respectively) must join the field data table (or field
* revision table) on a matching langcode *or* a bundle where the field is
* untranslatable. The following example views data achieves this for a node
* field named 'field_tags' which is translatable on an 'article' node type, but
* not on the 'news' and 'page' node types:
*
* @code
* $data['node__field_tags']['table']['join']['node_field_data'] = [
* 'join_id' => 'field_or_language_join',
* 'table' => 'node__field_tags',
* 'left_field' => 'nid',
* 'field' => 'entity_id',
* 'extra' => [
* [
* 'field' => 'deleted',
* 'value' => 0,
* 'numeric' => TRUE,
* ],
* [
* 'left_field' => 'langcode',
* 'field' => 'langcode',
* ],
* [
* 'field' => 'bundle',
* 'value' => ['news', 'page'],
* ],
* ],
* ];
* @endcode
*
* The resulting join condition for this example would be the following:
*
* @code
* ON node__field_tags.deleted = 0
* AND (
* node_field_data.langcode = node__field_tags.langcode
* OR node__field.tags.bundle IN ['news', 'page']
* )
* @endcode
*
* @see views_field_default_views_data()
*
* @ingroup views_join_handlers
*
* @ViewsJoin("field_or_language_join")
*/
class FieldOrLanguageJoin extends JoinPluginBase {
/**
* {@inheritdoc}
*/
protected function joinAddExtra(&$arguments, &$condition, $table, SelectInterface $select_query, $left_table = NULL) {
if (empty($this->extra)) {
return;
}
if (is_array($this->extra)) {
$extras = [];
foreach ($this->extra as $extra) {
$extras[] = $this->buildExtra($extra, $arguments, $table, $select_query, $left_table);
}
// Remove and store the langcode OR bundle join condition extra.
$language_bundle_conditions = [];
foreach ($extras as $key => $extra) {
if (strpos($extra, '.langcode') !== FALSE || strpos($extra, '.bundle') !== FALSE) {
$language_bundle_conditions[] = $extra;
unset($extras[$key]);
}
}
if (count($extras) > 1) {
$condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
}
elseif ($extras) {
$condition .= ' AND ' . array_shift($extras);
}
// Tack on the langcode OR bundle join condition extra.
if (!empty($language_bundle_conditions)) {
$condition .= ' AND (' . implode(' OR ', $language_bundle_conditions) . ')';
}
}
elseif (is_string($this->extra)) {
$condition .= " AND ($this->extra)";
}
}
}

View file

@ -2,6 +2,7 @@
namespace Drupal\views\Plugin\views\join;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Plugin\PluginBase;
/**
@ -225,7 +226,7 @@ class JoinPluginBase extends PluginBase implements JoinPluginInterface {
// Merge in some default values.
$configuration += [
'type' => 'LEFT',
'extra_operator' => 'AND'
'extra_operator' => 'AND',
];
$this->configuration = $configuration;
@ -261,12 +262,13 @@ class JoinPluginBase extends PluginBase implements JoinPluginInterface {
}
if ($this->leftTable) {
$left = $view_query->getTableInfo($this->leftTable);
$left_field = "$left[alias].$this->leftField";
$left_table = $view_query->getTableInfo($this->leftTable);
$left_field = "$left_table[alias].$this->leftField";
}
else {
// This can be used if left_field is a formula or something. It should be used only *very* rarely.
$left_field = $this->leftField;
$left_table = NULL;
}
$condition = "$left_field = $table[alias].$this->field";
@ -274,89 +276,124 @@ class JoinPluginBase extends PluginBase implements JoinPluginInterface {
// Tack on the extra.
if (isset($this->extra)) {
if (is_array($this->extra)) {
$extras = [];
foreach ($this->extra as $info) {
// Do not require 'value' to be set; allow for field syntax instead.
$info += [
'value' => NULL,
];
// Figure out the table name. Remember, only use aliases provided
// if at all possible.
$join_table = '';
if (!array_key_exists('table', $info)) {
$join_table = $table['alias'] . '.';
}
elseif (isset($info['table'])) {
// If we're aware of a table alias for this table, use the table
// alias instead of the table name.
if (isset($left) && $left['table'] == $info['table']) {
$join_table = $left['alias'] . '.';
}
else {
$join_table = $info['table'] . '.';
}
}
// Convert a single-valued array of values to the single-value case,
// and transform from IN() notation to = notation
if (is_array($info['value']) && count($info['value']) == 1) {
$info['value'] = array_shift($info['value']);
}
if (is_array($info['value'])) {
// We use an SA-CORE-2014-005 conformant placeholder for our array
// of values. Also, note that the 'IN' operator is implicit.
// @see https://www.drupal.org/node/2401615.
$operator = !empty($info['operator']) ? $info['operator'] : 'IN';
$placeholder = ':views_join_condition_' . $select_query->nextPlaceholder() . '[]';
$placeholder_sql = "( $placeholder )";
}
else {
// With a single value, the '=' operator is implicit.
$operator = !empty($info['operator']) ? $info['operator'] : '=';
$placeholder = $placeholder_sql = ':views_join_condition_' . $select_query->nextPlaceholder();
}
// Set 'field' as join table field if available or set 'left field' as
// join table field is not set.
if (isset($info['field'])) {
$join_table_field = "$join_table$info[field]";
// Allow the value to be set either with the 'value' element or
// with 'left_field'.
if (isset($info['left_field'])) {
$placeholder_sql = "$left[alias].$info[left_field]";
}
else {
$arguments[$placeholder] = $info['value'];
}
}
// Set 'left field' as join table field is not set.
else {
$join_table_field = "$left[alias].$info[left_field]";
$arguments[$placeholder] = $info['value'];
}
// Render out the SQL fragment with parameters.
$extras[] = "$join_table_field $operator $placeholder_sql";
}
if ($extras) {
if (count($extras) == 1) {
$condition .= ' AND ' . array_shift($extras);
}
else {
$condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
}
}
}
elseif ($this->extra && is_string($this->extra)) {
$condition .= " AND ($this->extra)";
}
$this->joinAddExtra($arguments, $condition, $table, $select_query, $left_table);
}
$select_query->addJoin($this->type, $right_table, $table['alias'], $condition, $arguments);
}
}
/**
* Adds the extras to the join condition.
*
* @param array $arguments
* Array of query arguments.
* @param string $condition
* The condition to be built.
* @param array $table
* The right table.
* @param \Drupal\Core\Database\Query\SelectInterface $select_query
* The current select query being built.
* @param array $left_table
* The left table.
*/
protected function joinAddExtra(&$arguments, &$condition, $table, SelectInterface $select_query, $left_table = NULL) {
if (is_array($this->extra)) {
$extras = [];
foreach ($this->extra as $info) {
$extras[] = $this->buildExtra($info, $arguments, $table, $select_query, $left_table);
}
/**
* @}
*/
if ($extras) {
if (count($extras) == 1) {
$condition .= ' AND ' . array_shift($extras);
}
else {
$condition .= ' AND (' . implode(' ' . $this->extraOperator . ' ', $extras) . ')';
}
}
}
elseif ($this->extra && is_string($this->extra)) {
$condition .= " AND ($this->extra)";
}
}
/**
* Builds a single extra condition.
*
* @param array $info
* The extra information. See JoinPluginBase::$extra for details.
* @param array $arguments
* Array of query arguments.
* @param array $table
* The right table.
* @param \Drupal\Core\Database\Query\SelectInterface $select_query
* The current select query being built.
* @param array $left
* The left table.
*
* @return string
* The extra condition
*/
protected function buildExtra($info, &$arguments, $table, SelectInterface $select_query, $left) {
// Do not require 'value' to be set; allow for field syntax instead.
$info += [
'value' => NULL,
];
// Figure out the table name. Remember, only use aliases provided
// if at all possible.
$join_table = '';
if (!array_key_exists('table', $info)) {
$join_table = $table['alias'] . '.';
}
elseif (isset($info['table'])) {
// If we're aware of a table alias for this table, use the table
// alias instead of the table name.
if (isset($left) && $left['table'] == $info['table']) {
$join_table = $left['alias'] . '.';
}
else {
$join_table = $info['table'] . '.';
}
}
// Convert a single-valued array of values to the single-value case,
// and transform from IN() notation to = notation
if (is_array($info['value']) && count($info['value']) == 1) {
$info['value'] = array_shift($info['value']);
}
if (is_array($info['value'])) {
// We use an SA-CORE-2014-005 conformant placeholder for our array
// of values. Also, note that the 'IN' operator is implicit.
// @see https://www.drupal.org/node/2401615.
$operator = !empty($info['operator']) ? $info['operator'] : 'IN';
$placeholder = ':views_join_condition_' . $select_query->nextPlaceholder() . '[]';
$placeholder_sql = "( $placeholder )";
}
else {
// With a single value, the '=' operator is implicit.
$operator = !empty($info['operator']) ? $info['operator'] : '=';
$placeholder = $placeholder_sql = ':views_join_condition_' . $select_query->nextPlaceholder();
}
// Set 'field' as join table field if available or set 'left field' as
// join table field is not set.
if (isset($info['field'])) {
$join_table_field = "$join_table$info[field]";
// Allow the value to be set either with the 'value' element or
// with 'left_field'.
if (isset($info['left_field'])) {
$placeholder_sql = "$left[alias].$info[left_field]";
}
else {
$arguments[$placeholder] = $info['value'];
}
}
// Set 'left field' as join table field is not set.
else {
$join_table_field = "$left[alias].$info[left_field]";
$arguments[$placeholder] = $info['value'];
}
// Render out the SQL fragment with parameters.
return "$join_table_field $operator $placeholder_sql";
}
}

View file

@ -114,12 +114,12 @@ abstract class PagerPluginBase extends PluginBase {
/**
* Provide the default form form for validating options
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) { }
public function validateOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Provide the default form form for submitting options
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) { }
public function submitOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Return a string to display as the clickable title for the
@ -156,6 +156,8 @@ abstract class PagerPluginBase extends PluginBase {
if (!empty($this->options['offset'])) {
$this->total_items -= $this->options['offset'];
}
// Prevent from being negative.
$this->total_items = max(0, $this->total_items);
return $this->total_items;
}
@ -173,22 +175,22 @@ abstract class PagerPluginBase extends PluginBase {
*
* This is called during the build phase and can directly modify the query.
*/
public function query() { }
public function query() {}
/**
* Perform any needed actions just prior to the query executing.
*/
public function preExecute(&$query) { }
public function preExecute(&$query) {}
/**
* Perform any needed actions just after the query executing.
*/
public function postExecute(&$result) { }
public function postExecute(&$result) {}
/**
* Perform any needed actions just before rendering.
*/
public function preRender(&$result) { }
public function preRender(&$result) {}
/**
* Return the renderable array of the pager.
@ -199,7 +201,7 @@ abstract class PagerPluginBase extends PluginBase {
* Any extra GET parameters that should be retained, such as exposed
* input.
*/
public function render($input) { }
public function render($input) {}
/**
* Determine if there are more records available.
@ -211,11 +213,11 @@ abstract class PagerPluginBase extends PluginBase {
&& $this->total_items > (intval($this->current_page) + 1) * $this->getItemsPerPage();
}
public function exposedFormAlter(&$form, FormStateInterface $form_state) { }
public function exposedFormAlter(&$form, FormStateInterface $form_state) {}
public function exposedFormValidate(&$form, FormStateInterface $form_state) { }
public function exposedFormValidate(&$form, FormStateInterface $form_state) {}
public function exposedFormSubmit(&$form, FormStateInterface $form_state, &$exclude) { }
public function exposedFormSubmit(&$form, FormStateInterface $form_state, &$exclude) {}
public function usesExposed() {
return FALSE;

View file

@ -132,7 +132,6 @@ abstract class SqlBase extends PagerPluginBase implements CacheableDependencyInt
],
];
$form['expose']['items_per_page_options_all'] = [
'#type' => 'checkbox',
'#title' => $this->t('Allow user to display all items'),
@ -234,7 +233,6 @@ abstract class SqlBase extends PagerPluginBase implements CacheableDependencyInt
$this->view->query->setOffset($offset);
}
/**
* Set the current page.
*
@ -362,7 +360,7 @@ abstract class SqlBase extends PagerPluginBase implements CacheableDependencyInt
public function exposedFormValidate(&$form, FormStateInterface $form_state) {
if (!$form_state->isValueEmpty('offset') && trim($form_state->getValue('offset'))) {
if (!is_numeric($form_state->getValue('offset')) || $form_state->getValue('offset') < 0) {
$form_state->setErrorByName('offset', $this->t('Offset must be an number greater or equal than 0.'));
$form_state->setErrorByName('offset', $this->t('Offset must be a number greater than or equal to 0.'));
}
}
}

View file

@ -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);
}

View file

@ -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)";
}
}
}

View file

@ -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");
}
}

View file

@ -54,7 +54,7 @@ abstract class QueryPluginBase extends PluginBase implements CacheableDependency
* @param $get_count
* Provide a countquery if this is true, otherwise provide a normal query.
*/
public function query($get_count = FALSE) { }
public function query($get_count = FALSE) {}
/**
* Let modules modify the query just prior to finalizing it.
@ -62,7 +62,7 @@ abstract class QueryPluginBase extends PluginBase implements CacheableDependency
* @param view $view
* The view which is executed.
*/
public function alter(ViewExecutable $view) { }
public function alter(ViewExecutable $view) {}
/**
* Builds the necessary info to execute the query.
@ -70,7 +70,7 @@ abstract class QueryPluginBase extends PluginBase implements CacheableDependency
* @param view $view
* The view which is executed.
*/
public function build(ViewExecutable $view) { }
public function build(ViewExecutable $view) {}
/**
* Executes the query and fills the associated view object with according
@ -85,7 +85,7 @@ abstract class QueryPluginBase extends PluginBase implements CacheableDependency
* @param view $view
* The view which is executed.
*/
public function execute(ViewExecutable $view) { }
public function execute(ViewExecutable $view) {}
/**
* Add a signature to the query, if such a thing is feasible.
@ -96,18 +96,18 @@ abstract class QueryPluginBase extends PluginBase implements CacheableDependency
* @param view $view
* The view which is executed.
*/
public function addSignature(ViewExecutable $view) { }
public function addSignature(ViewExecutable $view) {}
/**
* Get aggregation info for group by queries.
*
* If NULL, aggregation is not allowed.
*/
public function getAggregationInfo() { }
public function getAggregationInfo() {}
public function validateOptionsForm(&$form, FormStateInterface $form_state) { }
public function validateOptionsForm(&$form, FormStateInterface $form_state) {}
public function submitOptionsForm(&$form, FormStateInterface $form_state) { }
public function submitOptionsForm(&$form, FormStateInterface $form_state) {}
public function summaryTitle() {
return $this->t('Settings');
@ -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;
}
}
/**

View file

@ -5,8 +5,10 @@ namespace Drupal\views\Plugin\views\query;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\views\Plugin\views\join\JoinPluginBase;
@ -60,6 +62,8 @@ class Sql extends QueryPluginBase {
/**
* The default operator to use when connecting the WHERE groups. May be
* AND or OR.
*
* @var string
*/
protected $groupOperator = 'AND';
@ -81,9 +85,14 @@ class Sql extends QueryPluginBase {
/**
* A flag as to whether or not to make the primary field distinct.
*
* @var bool
*/
public $distinct = FALSE;
/**
* @var bool
*/
protected $hasAggregate = FALSE;
/**
@ -115,6 +124,20 @@ class Sql extends QueryPluginBase {
*/
protected $entityTypeManager;
/**
* The database-specific date handler.
*
* @var \Drupal\views\Plugin\views\query\DateSqlInterface
*/
protected $dateSql;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* Constructs a Sql object.
*
@ -126,19 +149,30 @@ 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.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/
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, MessengerInterface $messenger) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
$this->dateSql = $date_sql;
$this->messenger = $messenger;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager')
$container->get('entity_type.manager'),
$container->get('views.date_sql'),
$container->get('messenger')
);
}
@ -154,7 +188,7 @@ class Sql extends QueryPluginBase {
'link' => NULL,
'table' => $base_table,
'alias' => $base_table,
'base' => $base_table
'base' => $base_table,
];
// init the table queue with our primary table.
@ -179,6 +213,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).
*
@ -830,7 +885,7 @@ class Sql extends QueryPluginBase {
* @code
* $this->query->addWhere(
* $this->options['group'],
* db_or()
* (new Condition('OR'))
* ->condition($field, $value, 'NOT IN')
* ->condition($field, $value, 'IS NULL')
* );
@ -990,7 +1045,7 @@ class Sql extends QueryPluginBase {
$this->orderby[] = [
'field' => $as,
'direction' => strtoupper($order)
'direction' => strtoupper($order),
];
}
@ -1056,13 +1111,13 @@ class Sql extends QueryPluginBase {
$has_arguments = FALSE;
$has_filter = FALSE;
$main_group = db_and();
$filter_group = $this->groupOperator == 'OR' ? db_or() : db_and();
$main_group = new Condition('AND');
$filter_group = $this->groupOperator == 'OR' ? new Condition('OR') : new Condition('AND');
foreach ($this->$where as $group => $info) {
if (!empty($info['conditions'])) {
$sub_group = $info['type'] == 'OR' ? db_or() : db_and();
$sub_group = $info['type'] == 'OR' ? new Condition('OR') : new Condition('AND');
foreach ($info['conditions'] as $clause) {
if ($clause['operator'] == 'formula') {
$has_condition = TRUE;
@ -1458,7 +1513,7 @@ class Sql extends QueryPluginBase {
if (!empty($this->limit) || !empty($this->offset)) {
// We can't have an offset without a limit, so provide a very large limit instead.
$limit = intval(!empty($this->limit) ? $this->limit : 999999);
$limit = intval(!empty($this->limit) ? $this->limit : 999999);
$offset = intval(!empty($this->offset) ? $this->offset : 0);
$query->range($offset, $limit);
}
@ -1468,7 +1523,7 @@ class Sql extends QueryPluginBase {
// Setup the result row objects.
$view->result = iterator_to_array($result);
array_walk($view->result, function(ResultRow $row, $index) {
array_walk($view->result, function (ResultRow $row, $index) {
$row->index = $index;
});
@ -1482,7 +1537,7 @@ class Sql extends QueryPluginBase {
catch (DatabaseExceptionWrapper $e) {
$view->result = [];
if (!empty($view->live_preview)) {
drupal_set_message($e->getMessage(), 'error');
$this->messenger->addError($e->getMessage());
}
else {
throw new DatabaseExceptionWrapper("Exception in {$view->storage->label()}[{$view->storage->id()}]: {$e->getMessage()}");
@ -1738,7 +1793,7 @@ class Sql extends QueryPluginBase {
'filter' => 'groupby_numeric',
'sort' => 'groupby_numeric',
],
]
],
];
}
@ -1754,175 +1809,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);
}
}

View file

@ -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)";
}
}
}

View file

@ -53,7 +53,7 @@ class EntityReverse extends RelationshipPluginBase {
'left_field' => $left_field,
'table' => $this->definition['field table'],
'field' => $this->definition['field field'],
'adjusted' => TRUE
'adjusted' => TRUE,
];
if (!empty($this->options['required'])) {
$first['type'] = 'INNER';
@ -71,7 +71,6 @@ class EntityReverse extends RelationshipPluginBase {
}
$first_join = $this->joinManager->createInstance($id, $first);
$this->first_alias = $this->query->addTable($this->definition['field table'], $this->relationship, $first_join);
// Second, relate the field table to the entity specified using
@ -81,7 +80,7 @@ class EntityReverse extends RelationshipPluginBase {
'left_field' => 'entity_id',
'table' => $this->definition['base'],
'field' => $this->definition['base field'],
'adjusted' => TRUE
'adjusted' => TRUE,
];
if (!empty($this->options['required'])) {

View file

@ -115,7 +115,6 @@ class GroupwiseMax extends RelationshipPluginBase {
'#default_value' => $this->options['subquery_namespace'],
];
// WIP: This stuff doesn't work yet: namespacing issues.
// A list of suitable views to pick one as the subview.
$views = ['' => '- None -'];
@ -178,7 +177,7 @@ class GroupwiseMax extends RelationshipPluginBase {
* - subquery_order: either ASC or DESC.
*
* @return string
* The subquery SQL string, ready for use in the main query.
* The subquery SQL string, ready for use in the main query.
*/
protected function leftQuery($options) {
// Either load another view, or create one on the fly.

View file

@ -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;
}
}

View file

@ -37,7 +37,7 @@ class EntityReference extends Fields {
parent::buildOptionsForm($form, $form_state);
// Expand the description of the 'Inline field' checkboxes.
$form['inline']['#description'] .= '<br />' . $this->t("<strong>Note:</strong> In 'Entity Reference' displays, all fields will be displayed inline unless an explicit selection of inline fields is made here." );
$form['inline']['#description'] .= '<br />' . $this->t("<strong>Note:</strong> In 'Entity Reference' displays, all fields will be displayed inline unless an explicit selection of inline fields is made here.");
}
/**

View file

@ -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
*/

View file

@ -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
*/

View file

@ -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
*/
@ -122,13 +122,13 @@ abstract class RowPluginBase extends PluginBase {
/**
* Validate the options form.
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) { }
public function validateOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* Perform any necessary changes to the form values prior to storage.
* There is no need for this function to actually store the data.
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) { }
public function submitOptionsForm(&$form, FormStateInterface $form_state) {}
/**
* {@inheritdoc}
@ -151,7 +151,7 @@ abstract class RowPluginBase extends PluginBase {
* @param $result
* The full array of results from the query.
*/
public function preRender($result) { }
public function preRender($result) {}
/**
* Render a row object. This usually passes through to a theme template

View file

@ -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
*/

View file

@ -28,7 +28,9 @@ abstract class SortPluginBase extends HandlerBase implements CacheableDependency
/**
* Determine if a sort can be exposed.
*/
public function canExpose() { return TRUE; }
public function canExpose() {
return TRUE;
}
/**
* Called to add the sort to a query.
@ -175,9 +177,9 @@ abstract class SortPluginBase extends HandlerBase implements CacheableDependency
}
}
protected function sortValidate(&$form, FormStateInterface $form_state) { }
protected function sortValidate(&$form, FormStateInterface $form_state) {}
public function sortSubmit(&$form, FormStateInterface $form_state) { }
public function sortSubmit(&$form, FormStateInterface $form_state) {}
/**
* Provide a list of options for the default sort form.

View file

@ -19,9 +19,7 @@ namespace Drupal\views\Plugin\views\style;
class DefaultStyle extends StylePluginBase {
/**
* Does the style plugin allows to use style plugins.
*
* @var bool
* {@inheritdoc}
*/
protected $usesRowPlugin = TRUE;

View file

@ -21,9 +21,7 @@ use Drupal\Core\Form\FormStateInterface;
class Grid extends StylePluginBase {
/**
* Does the style plugin allows to use style plugins.
*
* @var bool
* {@inheritdoc}
*/
protected $usesRowPlugin = TRUE;

View file

@ -20,9 +20,7 @@ use Drupal\Core\Form\FormStateInterface;
class HtmlList extends StylePluginBase {
/**
* Does the style plugin allows to use style plugins.
*
* @var bool
* {@inheritdoc}
*/
protected $usesRowPlugin = TRUE;

View file

@ -20,9 +20,7 @@ use Drupal\Core\Url;
class Opml extends StylePluginBase {
/**
* Does the style plugin for itself support to add fields to its output.
*
* @var bool
* {@inheritdoc}
*/
protected $usesRowPlugin = TRUE;
@ -58,8 +56,8 @@ class Opml extends StylePluginBase {
*/
public function render() {
if (empty($this->view->rowPlugin)) {
debug('Drupal\views\Plugin\views\style\Opml: Missing row plugin');
return;
trigger_error('Drupal\views\Plugin\views\style\Opml: Missing row plugin', E_WARNING);
return [];
}
$rows = [];

Some files were not shown because too many files have changed in this diff Show more