Move into nested docroot

This commit is contained in:
Rob Davies 2017-02-13 15:31:17 +00:00
parent 83a0d3a149
commit c8b70abde9
13405 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,46 @@
display_extenders: { }
skip_cache: false
sql_signature: false
ui:
show:
additional_queries: false
advanced_column: false
master_display: false
performance_statistics: false
preview_information: true
sql_query:
enabled: false
where: above
display_embed: false
always_live_preview: true
exposed_filter_any_label: old_any
field_rewrite_elements:
div: DIV
span: SPAN
h1: H1
h2: H2
h3: H3
h4: H4
h5: H5
h6: H6
p: P
header: HEADER
footer: FOOTER
article: ARTICLE
section: SECTION
aside: ASIDE
details: DETAILS
blockquote: BLOCKQUOTE
figure: FIGURE
address: ADDRESS
code: CODE
pre: PRE
var: VAR
samp: SAMP
kbd: KBD
strong: STRONG
em: EM
del: DEL
ins: INS
q: Q
s: S

View file

@ -0,0 +1,5 @@
# Schema for the views access plugins.
views.access.none:
type: mapping
label: 'None'

View file

@ -0,0 +1,80 @@
# Schema for the views area plugins.
views.area.*:
type: views_area
label: 'Default area'
views.area.entity:
type: views_area
label: 'Entity'
mapping:
target:
type: string
label: 'The target entity'
view_mode:
type: string
label: 'View mode'
tokenize:
type: boolean
label: 'Should replacement tokens be used from the first row'
bypass_access:
type: boolean
label: 'Bypass access checks'
views.area.text:
type: views_area
label: 'Text'
mapping:
content:
type: text_format
label: 'The formatted text of the area'
tokenize:
type: boolean
label: 'Should replacement tokens be used from the first row'
views.area.text_custom:
type: views_area
label: 'Text custom'
mapping:
content:
type: text
label: 'The shown text of the area'
tokenize:
type: boolean
label: 'Should replacement tokens be used from the first row'
views.area.result:
type: views_area
label: 'Result'
mapping:
content:
type: text
label: 'The shown text of the result summary area'
views.area.title:
type: views_area
label: 'Title'
mapping:
title:
type: label
label: 'The title which will be overridden for the page'
views.area.view:
type: views_area
label: 'View'
mapping:
view_to_insert:
type: string
label: 'View to insert'
inherit_arguments:
type: boolean
label: 'Inherit contextual filters'
views.area.http_status_code:
type: views_area
label: 'HTTP status code'
mapping:
status_code:
type: integer
label: 'HTTP status code'

View file

@ -0,0 +1,152 @@
# Schema for the views argument plugins.
views.argument.*:
type: views_argument
label: 'Default argument'
views.argument.many_to_one:
type: views_argument
label: 'Many to one'
mapping:
break_phrase:
type: boolean
label: 'Allow multiple values'
add_table:
type: boolean
label: 'Allow multiple filter values to work together'
require_value:
type: boolean
label: 'Do not display items with no value in summary'
reduce_duplicates:
type: boolean
label: 'Reduce duplicates'
views.argument.null:
type: views_argument
label: 'Null'
mapping:
must_not_be:
type: boolean
label: 'Fail basic validation if any argument is given'
views.argument.numeric:
type: views_argument
label: 'Numeric'
mapping:
break_phrase:
type: boolean
label: 'Allow multiple values'
not:
type: boolean
label: 'Exclude'
views.argument.string:
type: views_argument
label: 'String'
mapping:
glossary:
type: boolean
label: 'Glossary mode'
limit:
type: integer
label: 'Character limit'
case:
type: string
label: 'Case'
path_case:
type: string
label: 'Case in path'
transform_dash:
type: boolean
label: 'Transform spaces to dashes in URL'
break_phrase:
type: boolean
label: 'Allow multiple values'
add_table:
type: boolean
label: 'Allow multiple filter values to work together'
require_value:
type: boolean
label: 'Do not display items with no value in summary'
views.argument.broken:
type: views_argument
label: 'Broken'
views.argument.date:
type: views_argument
label: 'Date'
mapping:
date:
type: string
label: 'Date'
node_created:
type: string
label: 'Node Creation Time'
node_changed:
type: string
label: 'Node Update Time'
views.argument.date_day:
type: views.argument.date
label: 'Day Date'
mapping:
day:
type: string
label: 'Day'
views.argument.formula:
type: views_argument
label: 'Formula'
mapping:
placeholder:
type: string
label: 'Place Holder'
formula:
type: string
label: 'Formula Used'
views.argument.date_fulldate:
type: views.argument.date
label: 'Full Date'
mapping:
created:
type: string
label: 'Full Date'
views.argument.groupby_numeric:
type: views_argument
label: 'Group by Numeric'
views.argument.date_month:
type: views.argument.date
label: 'Month Date'
mapping:
month:
type: string
label: 'Month'
views.argument.standard:
type: views_argument
label: 'Standard'
views.argument.date_week:
type: views.argument.date
label: 'Week Date'
views.argument.date_year:
type: views.argument.date
label: 'Year Date'
views.argument.date_year_month:
type: views.argument.date
label: 'YearMonthDate'
mapping:
created:
type: string
label: 'Date Year month'
views.argument.language:
type: views_argument
label: 'Language'

View file

@ -0,0 +1,38 @@
# Schema for the views default arguments.
views.argument_default.*:
type: mapping
label: 'Base default argument'
views.argument_default.fixed:
type: mapping
label: 'Fixed'
mapping:
argument:
type: string
label: 'Fixed value'
views.argument_default.raw:
type: mapping
label: 'Raw value from URL'
mapping:
index:
type: integer
label: 'Path component'
use_alias:
type: boolean
label: 'Use path alias'
views.argument_default.query_parameter:
type: mapping
label: 'Query parameter'
mapping:
query_param:
type: string
label: 'Parameter'
fallback:
type: string
label: 'Fallback value'
multiple:
type: string
label: 'Multiple values'

View file

@ -0,0 +1,41 @@
# Schema for the views argument validators.
views.argument_validator.none:
type: sequence
label: 'Basic validation'
sequence:
type: string
views.argument_validator.php:
type: mapping
label: 'PHP Code'
mapping:
code:
type: string
label: 'PHP validate code'
views.argument_validator.*:
type: mapping
label: 'Default argument validator'
views.argument_validator_entity:
type: mapping
mapping:
bundles:
type: sequence
label: 'Bundles'
sequence:
type: string
label: 'Bundle'
access:
type: boolean
label: 'Access'
operation:
type: string
label: 'Access operation to check'
multiple:
type: integer
label: 'Multiple arguments'
views.argument_validator.entity:*:
type: views.argument_validator_entity

View file

@ -0,0 +1,38 @@
# Schema for the views cache.
views.cache.none:
type: views_cache
label: 'No caching'
mapping:
options:
type: sequence
label: 'Options'
views.cache.tag:
type: views_cache
label: 'Tag based caching'
mapping:
options:
type: sequence
label: 'Options'
views.cache.time:
type: views_cache
label: 'Time based caching'
mapping:
options:
type: mapping
label: 'Cache options'
mapping:
results_lifespan:
type: integer
label: 'The length of time raw query results should be cached.'
results_lifespan_custom:
type: integer
label: 'Length of time in seconds raw query results should be cached.'
output_lifespan:
type: integer
label: 'The length of time rendered HTML output should be cached.'
output_lifespan_custom:
type: integer
label: 'Length of time in seconds rendered HTML output should be cached.'

View file

@ -0,0 +1,822 @@
# Basic data types for views.
views_display:
type: mapping
label: 'Display options'
mapping:
enabled:
type: boolean
label: 'Status'
title:
type: text
label: 'Display title'
format:
type: string
label: 'Format'
fields:
type: sequence
label: 'Fields'
sequence:
type: views.field.[plugin_id]
pager:
type: mapping
label: 'Pager'
mapping:
type:
type: string
label: 'Pager type'
options:
type: views.pager.[%parent.type]
exposed_form:
type: mapping
label: 'Exposed form'
mapping:
type:
type: string
label: 'Exposed form type'
options:
label: 'Options'
type: views.exposed_form.[%parent.type]
access:
type: mapping
label: 'Access'
mapping:
type:
type: string
label: 'Access type'
options:
type: views.access.[%parent.type]
cache:
type: views.cache.[type]
empty:
type: sequence
label: 'No results behavior'
sequence:
type: views.area.[plugin_id]
sorts:
type: sequence
label: 'Sorts'
sequence:
type: views.sort.[plugin_id]
arguments:
type: sequence
label: 'Arguments'
sequence:
type: views.argument.[plugin_id]
filters:
type: sequence
label: 'Filters'
sequence:
type: views.filter.[plugin_id]
filter_groups:
type: mapping
label: 'Groups'
mapping:
operator:
type: string
label: 'Operator'
groups:
type: sequence
label: 'Groups'
sequence:
type: string
label: 'Operator'
style:
type: mapping
label: 'Format'
mapping:
type:
type: string
label: 'Type'
options:
type: views.style.[%parent.type]
row:
type: mapping
label: 'Row'
mapping:
type:
type: string
label: 'Row type'
options:
type: views.row.[%parent.type]
query:
type: mapping
label: 'Query'
mapping:
type:
type: string
label: 'Query type'
options:
type: views.query.[%parent.type]
defaults:
type: mapping
label: 'Defaults'
mapping:
empty:
type: boolean
label: 'Empty'
access:
type: boolean
label: 'Access restrictions'
cache:
type: boolean
label: 'Caching'
query:
type: boolean
label: 'Query options'
title:
type: boolean
label: 'Title'
css_class:
type: boolean
label: 'CSS class'
display_description:
type: boolean
label: 'Administrative description'
use_ajax:
type: boolean
label: 'Use AJAX'
hide_attachment_summary:
type: boolean
label: 'Hide attachments when displaying a contextual filter summary'
show_admin_links:
type: boolean
label: 'Show contextual links'
pager:
type: boolean
label: 'Use pager'
use_more:
type: boolean
label: 'Create more link'
use_more_always:
type: boolean
label: 'Display ''more'' link only if there is more content'
use_more_text:
type: boolean
label: 'The text to display for the more link.'
exposed_form:
type: boolean
label: 'Exposed form style'
link_display:
type: boolean
label: 'Link display'
link_url:
type: boolean
label: 'Link URL'
group_by:
type: boolean
label: 'Aggregate'
style:
type: boolean
label: 'Style'
row:
type: boolean
label: 'Row'
relationships:
type: boolean
label: 'Relationships'
fields:
type: boolean
label: 'Fields'
sorts:
type: boolean
label: 'Sorts'
arguments:
type: boolean
label: 'Arguments'
filters:
type: boolean
label: 'Filters'
filter_groups:
type: boolean
label: 'Filter groups'
header:
type: boolean
label: 'Header'
footer:
type: boolean
label: 'Footer'
relationships:
type: sequence
label: 'Relationships'
sequence:
type: views.relationship.[plugin_id]
css_class:
type: string
label: 'CSS class'
use_ajax:
type: boolean
label: 'Use AJAX'
group_by:
type: boolean
label: 'Aggregate'
display_description:
type: label
label: 'Administrative description'
show_admin_links:
type: boolean
label: 'Show contextual links'
use_more:
type: boolean
label: 'Create more link'
use_more_always:
type: boolean
label: 'Display ''more'' link only if there is more content'
use_more_text:
type: label
label: 'The text to display for the more link.'
link_display:
type: string
label: 'Link display'
link_url:
type: string
label: 'Link URL'
header:
type: sequence
label: 'Header'
sequence:
type: views.area.[plugin_id]
footer:
type: sequence
label: 'Footer'
sequence:
type: views.area.[plugin_id]
display_comment:
type: label
label: 'Display comment'
hide_attachment_summary:
type: boolean
label: 'Hide attachments in summary'
rendering_language:
type: string
label: 'Entity language'
exposed_block:
type: boolean
label: 'Put the exposed form in a block'
display_extenders:
type: sequence
label: 'Display extenders'
sequence:
type: views.display_extender.[%key]
views_sort:
type: views_handler
label: 'Sort criteria'
mapping:
order:
type: string
label: 'Sort order'
expose:
type: views.sort_expose.[%parent.plugin_id]
exposed:
type: boolean
label: 'Expose this sort to visitors, to allow them to change it'
plugin_id:
type: string
label: 'Plugin ID'
views_sort_expose:
type: mapping
mapping:
label:
type: label
label: 'Label'
views_area:
type: views_handler
label: 'Area'
mapping:
label:
type: label
label: 'A string to identify the area instance in the admin UI.'
empty:
type: boolean
label: 'Should the area be displayed on empty results.'
plugin_id:
type: string
label: 'Plugin ID'
views_handler:
type: mapping
mapping:
id:
type: string
label: 'A unique ID per handler type'
table:
type: string
label: 'The views_data table for this handler'
field:
type: string
label: 'The views_data field for this handler'
relationship:
type: string
label: 'The ID of the relationship instance used by this handler'
group_type:
type: string
label: 'A sql aggregation type'
admin_label:
type: label
label: 'A string to identify the handler instance in the admin UI.'
entity_type:
type: string
label: 'The entity type'
entity_field:
type: string
label: 'The corresponding entity field'
plugin_id:
type: string
label: 'The plugin ID'
views_argument:
type: views_handler
label: 'Argument'
mapping:
default_action:
type: string
label: 'When the filter value is NOT available'
exception:
type: mapping
label: 'Exception value'
mapping:
value:
type: string
label: 'Value'
title_enable:
type: boolean
label: 'Override title'
title:
type: label
label: 'Title'
title_enable:
type: boolean
label: 'Override title'
title:
type: label
label: 'Overridden title'
default_argument_type:
type: string
label: 'Type'
default_argument_options:
type: views.argument_default.[%parent.default_argument_type]
label: 'Default argument options'
default_argument_skip_url:
type: boolean
label: 'Skip default argument for view URL'
summary_options:
type: views.style.[%parent.summary.format]
label: 'Summary options'
summary:
type: mapping
label: 'Display a summary'
mapping:
sort_order:
type: string
label: 'Sort order'
number_of_records:
type: integer
label: 'Sort by'
format:
type: string
label: 'Format'
specify_validation:
type: boolean
label: 'Specify validation criteria'
validate:
type: mapping
label: 'Validation settings'
mapping:
type:
type: string
label: 'Validator'
fail:
type: string
label: 'Action to take if filter value does not validate'
validate_options:
type: views.argument_validator.[%parent.validate.type]
label: 'Validate options'
glossary:
type: boolean
label: 'Glossary mode'
limit:
type: integer
label: 'Character limit'
case:
type: string
label: 'Case'
path_case:
type: string
label: 'Case in path'
transform_dash:
type: boolean
label: 'Transform spaces to dashes in URL'
break_phrase:
type: boolean
label: 'Allow multiple values'
plugin_id:
type: string
label: 'Plugin ID'
views_exposed_form:
type: mapping
mapping:
submit_button:
type: label
label: 'Submit button text'
reset_button:
type: boolean
label: 'Include reset button'
reset_button_label:
type: label
label: 'Reset button label'
exposed_sorts_label:
type: label
label: 'Exposed sorts label'
expose_sort_order:
type: boolean
label: 'Expose sort order'
sort_asc_label:
type: label
label: 'Ascending'
sort_desc_label:
type: label
label: 'Descending'
views_field:
type: views_handler
mapping:
label:
type: label
label: 'Create a label'
exclude:
type: boolean
label: 'Exclude from display'
alter:
type: mapping
label: 'Rewrite results'
mapping:
alter_text:
type: boolean
label: 'Override the output of this field with custom text'
text:
type: text
label: 'Text'
make_link:
type: boolean
label: 'Output this field as a custom link'
path:
type: string
label: 'Link path'
absolute:
type: boolean
label: 'Use absolute path'
external:
type: boolean
label: 'External server URL'
replace_spaces:
type: boolean
label: 'Replace spaces with dashes'
path_case:
type: string
label: 'Transform the case'
trim_whitespace:
type: boolean
label: 'Remove whitespace'
alt:
type: label
label: 'Title text'
rel:
type: string
label: 'Rel Text'
link_class:
type: string
label: 'Link class'
prefix:
type: label
label: 'Prefix text'
suffix:
type: label
label: 'Suffix text'
target:
type: string
label: 'Target'
nl2br:
type: boolean
label: 'Convert newlines to HTML <br> tags'
max_length:
type: integer
label: 'Maximum number of characters'
word_boundary:
type: boolean
label: 'Trim only on a word boundary'
ellipsis:
type: boolean
label: 'Add "…" at the end of trimmed text'
more_link:
type: boolean
label: 'Add a read-more link if output is trimmed'
more_link_text:
type: label
label: 'More link label'
more_link_path:
type: string
label: 'More link path'
strip_tags:
type: boolean
label: 'Strip HTML tags'
trim:
type: boolean
label: 'Trim this field to a maximum number of characters'
preserve_tags:
type: string
label: 'Preserve certain tags'
html:
type: boolean
label: 'Field can contain HTML'
element_type:
type: string
label: 'HTML element'
element_class:
type: string
label: 'CSS class'
element_label_type:
type: string
label: 'Label HTML element'
element_label_class:
type: string
label: 'CSS class'
element_label_colon:
type: boolean
label: 'Place a colon after the label'
element_wrapper_type:
type: string
label: 'Wrapper HTML element'
element_wrapper_class:
type: string
label: 'CSS class'
element_default_classes:
type: boolean
label: 'Add default classes'
empty:
type: string
label: 'No results text'
hide_empty:
type: boolean
label: 'Hide if empty'
empty_zero:
type: boolean
label: 'Count the number 0 as empty'
hide_alter_empty:
type: boolean
label: 'Hide rewriting if empty'
destination:
type: boolean
label: 'Append a destination query string to operation links.'
plugin_id:
type: string
label: 'Plugin ID'
views_pager:
type: mapping
label: 'Pager'
mapping:
offset:
type: integer
label: 'Offset'
items_per_page:
type: integer
label: 'Items per page'
views_pager_sql:
type: views_pager
label: 'SQL pager'
mapping:
items_per_page:
type: integer
label: 'Items per page'
total_pages:
type: integer
label: 'Number of pages'
id:
type: integer
label: 'Pager ID'
tags:
type: mapping
label: 'Pager link labels'
mapping:
next:
type: label
label: 'Next page link text'
previous:
type: label
label: 'Previous page link text'
quantity:
type: integer
label: 'Number of pager links visible'
expose:
type: mapping
label: 'Exposed options'
mapping:
items_per_page:
type: boolean
label: 'Items per page'
items_per_page_label:
type: label
label: 'Items per page label'
items_per_page_options:
type: string
label: 'Exposed items per page options'
items_per_page_options_all:
type: boolean
label: 'Include all items option'
items_per_page_options_all_label:
type: label
label: 'All items label'
offset:
type: boolean
label: 'Expose Offset'
offset_label:
type: label
label: 'Offset label'
views_style:
type: mapping
mapping:
grouping:
type: sequence
label: 'Grouping field number %i'
sequence:
type: mapping
label: 'Field'
mapping:
field:
type: string
label: 'Field'
rendered:
type: boolean
label: 'Use rendered output to group rows'
rendered_strip:
type: boolean
label: 'Remove tags from rendered output'
row_class:
type: string
label: 'Row class'
default_row_class:
type: boolean
label: 'Add views row classes'
uses_fields:
type: boolean
label: 'Force using fields'
views_filter:
type: views_handler
mapping:
operator:
type: string
label: 'Operator'
value:
type: views.filter_value.[%parent.plugin_id]
label: 'Value'
group:
type: integer
label: 'Group'
exposed:
type: boolean
label: 'Expose this filter to visitors, to allow them to change it'
expose:
type: mapping
label: 'Expose'
mapping:
operator_id:
type: string
label: 'Operator identifier'
label:
type: label
label: 'Label'
description:
type: label
label: 'Description'
use_operator:
type: boolean
label: 'Expose operator'
operator:
type: string
label: 'Operator'
identifier:
type: string
label: 'Filter identifier'
required:
type: boolean
label: 'Required'
remember:
type: boolean
label: 'Remember the last selection'
multiple:
type: boolean
label: 'Allow multiple selections'
remember_roles:
type: sequence
label: 'User roles'
sequence:
type: string
label: 'Role'
is_grouped:
type: boolean
label: 'Grouped filters'
group_info:
type: mapping
label: 'Group'
mapping:
label:
type: label
label: 'Label'
description:
type: label
label: 'Description'
identifier:
type: string
label: 'Identifier'
optional:
type: boolean
label: 'Optional'
widget:
type: string
label: 'Widget type'
multiple:
type: boolean
label: 'Allow multiple selections'
remember:
type: boolean
label: 'Remember'
default_group:
type: string
label: 'Default'
default_group_multiple:
type: sequence
label: 'Defaults'
sequence:
type: integer
label: 'Default'
group_items:
type: sequence
label: 'Group items'
sequence:
type: views.filter.group_item.[%parent.%parent.%parent.plugin_id]
label: 'Group item'
plugin_id:
type: string
label: 'Plugin ID'
views_filter_group_item:
type: mapping
label: 'Group item'
mapping:
title:
type: label
label: 'Label'
operator:
type: string
label: 'Operator'
value:
type: label
label: 'Value'
views_relationship:
type: views_handler
mapping:
admin_label:
type: string
label: 'Administrative title'
required:
type: boolean
label: 'Require this relationship'
views_query:
type: mapping
label: 'Query options'
views_row:
type: mapping
label: 'Row options'
mapping:
relationship:
type: string
label: 'Relationship'
views_entity_row:
type: views_row
mapping:
view_mode:
type: string
label: 'View mode'
views_cache:
type: mapping
label: 'Cache configuration'
mapping:
type:
type: string
label: 'Cache type'
views_display_extender:
type: mapping
label: 'Display extender settings'

View file

@ -0,0 +1,142 @@
# Schema for the views display plugins.
views.display.default:
type: views_display
label: 'Default display options'
views_display_path:
type: views_display
mapping:
path:
type: string
label: 'Page path'
route_name:
type: string
label: 'Route name'
views.display.page:
type: views_display_path
label: 'Page display options'
mapping:
menu:
type: mapping
label: 'Menu'
mapping:
type:
type: string
label: 'Type'
title:
type: text
label: 'Title'
description:
type: text
label: 'Description'
weight:
type: integer
label: 'Weight'
enabled:
type: boolean
label: 'Enabled'
expanded:
type: boolean
label: 'Expanded'
menu_name:
type: string
label: 'Menu name'
parent:
type: string
label: 'Parent'
context:
type: string
label: 'Context'
expanded:
type: boolean
label: 'Expanded'
tab_options:
type: mapping
label: 'Tab options'
mapping:
type:
type: string
label: 'Type'
title:
type: text
label: 'Title'
description:
type: text
label: 'Description'
weight:
type: integer
label: 'Weight'
menu_name:
type: string
label: 'Menu name'
views.display.block:
type: views_display
label: 'Block display options'
mapping:
block_description:
type: label
label: 'Block name'
block_category:
type: text
label: 'Block category'
block_hide_empty:
type: boolean
label: 'Hide block if no result/empty text'
allow:
type: mapping
label: 'Allow'
mapping:
items_per_page:
type: boolean
label: 'Items per page'
views.display.feed:
type: views_display_path
label: 'Feed display options'
mapping:
sitename_title:
type: boolean
label: 'Use the site name for the title'
displays:
type: sequence
label: 'The feed icon will be available only to the selected displays.'
sequence:
type: string
label: 'Display'
views.display.embed:
type: views_display
label: 'Embed display options'
views.display.attachment:
type: views_display
label: 'Attachment display options'
mapping:
displays:
type: sequence
label: 'Attach to'
sequence:
type: string
label: 'Display'
attachment_position:
type: string
label: 'Attachment position'
inherit_arguments:
type: boolean
label: 'Inherit contextual filters'
inherit_exposed_filters:
type: boolean
label: 'Inherit exposed filters'
inherit_pager:
type: boolean
label: 'Inherit pager'
render_pager:
type: boolean
label: 'Render pager'
views.display.entity_reference:
type: views_display
label: 'Entity Reference'

View file

@ -0,0 +1,22 @@
# Schema for the views entity reference selection plugins.
entity_reference_selection.views:
type: mapping
label: 'View handler settings'
mapping:
view:
type: mapping
label: 'View used to select the entities'
mapping:
view_name:
type: string
label: 'View name'
display_name:
type: string
label: 'Display name'
arguments:
type: sequence
label: 'View arguments'
sequence:
type: string
label: 'Argument'

View file

@ -0,0 +1,16 @@
# Schema for the views exposed form.
views.exposed_form.basic:
type: views_exposed_form
label: 'Basic'
views.exposed_form.input_required:
type: views_exposed_form
label: 'Input required'
mapping:
text_input_required:
type: text
label: 'Text on demand'
text_input_required_format:
type: string
label: 'Text on demand format'

View file

@ -0,0 +1,252 @@
# Schema for the views field plugins.
views.field.*:
type: views_field
label: 'Default field'
views.field.boolean:
type: views_field
label: 'Boolean'
mapping:
type:
type: string
label: 'Output format'
type_custom_true:
type: string
label: 'Custom output for TRUE'
type_custom_false:
type: string
label: 'Custom output for FALSE'
not:
type: boolean
label: 'Reverse'
views.field.broken:
type: views_field
label: 'Broken'
views.field.counter:
type: views_field
label: 'Counter'
mapping:
counter_start:
type: integer
label: 'Starting value'
views.field.custom:
type: views_field
label: 'Custom'
views.field.date:
type: views_field
label: 'Date'
mapping:
date_format:
type: string
label: 'Date format'
custom_date_format:
type: string
label: 'Custom date format'
timezone:
type: string
label: 'Timezone'
views.field.entity_label:
type: views_field
label: 'Entity label'
mapping:
link_to_entity:
type: boolean
label: 'Link to entity'
views.field.file_size:
type: views_field
label: 'File size'
mapping:
file_size_display:
type: string
label: 'File size display'
views.field.links:
type: views_field
label: 'Links'
mapping:
fields:
type: sequence
label: 'Fields'
sequence:
type: string
label: 'Field'
destination:
type: boolean
label: 'Include destination'
views.field.dropbutton:
type: views.field.links
label: 'Drop button'
views.field.machine_name:
type: views_field
label: 'Machine name'
mapping:
machine_name:
type: boolean
label: 'Output machine name'
views.field.markup:
type: views_field
label: 'Markup'
views.field.numeric:
type: views_field
label: 'Numeric'
mapping:
set_precision:
type: boolean
label: 'Round'
precision:
type: integer
label: 'Precision'
decimal:
type: string
label: 'Decimal point'
separator:
type: string
label: 'Thousands marker'
format_plural:
type: boolean
label: 'Format plural'
format_plural_string:
type: plural_label
label: 'Plural variants'
prefix:
type: label
label: 'Prefix'
suffix:
type: label
label: 'Suffix'
views.field.prerender_list:
type: views_field
label: 'List'
mapping:
type:
type: string
label: 'Display type'
separator:
type: string
label: 'Separator'
views.field.serialized:
type: views_field
label: 'Serialized'
mapping:
format:
type: string
label: 'Display format'
key:
type: string
label: 'Which key should be displayed'
views.field.standard:
type: views_field
label: 'Standard'
views.field.time_interval:
type: views_field
label: 'Time interval'
mapping:
granularity:
type: integer
label: 'Granularity'
views.field.url:
type: views_field
label: 'URL'
mapping:
display_as_link:
type: boolean
label: 'Display as link'
views.field.language:
type: views_field
label: 'Language'
mapping:
native_language:
type: boolean
label: 'Display in native language'
views.field.rendered_entity:
type: views_field
label: 'Rendered entity'
mapping:
view_mode:
type: string
label: 'View mode'
views.field.entity_link:
type: views_field
label: 'Entity link'
mapping:
text:
type: label
label: 'Text to display'
views.field.entity_link_delete:
type: views.field.entity_link
label: 'Entity delete link'
views.field.entity_link_edit:
type: views.field.entity_link
label: 'Entity edit link'
views.field.bulk_form:
type: views_field_bulk_form
label: 'Bulk form'
views.field.field:
type: views_field
label: 'Views entity field handler'
mapping:
click_sort_column:
type: string
label: 'Column used for click sorting'
type:
type: string
label: 'Formatter'
settings:
label: 'Settings'
type: field.formatter.settings.[%parent.type]
group_column:
type: string
label: 'Group by column'
group_columns:
type: sequence
label: 'Group by columns'
sequence:
type: string
label: 'Column'
group_rows:
type: boolean
label: 'Display all values in the same row'
delta_limit:
type: integer
label: 'Field'
delta_offset:
type: integer
label: 'Offset'
delta_reversed:
type: boolean
label: 'Reversed'
delta_first_last:
type: boolean
label: 'First and last only'
multi_type:
type: string
label: 'Display type'
separator:
type: label
label: 'Separator'
field_api_classes:
type: boolean
label: 'Use field template'

View file

@ -0,0 +1,133 @@
# Schema for the views filter plugins.
views.filter.*:
type: views_filter
label: 'Default filter'
views.filter.boolean:
type: views_filter
label: 'Boolean'
views_filter_boolean_string:
type: views_filter
label: 'Boolean string'
views.filter.broken:
type: views_filter
label: 'Broken'
views.filter.bundle:
type: views.filter.in_operator
label: 'Bundle'
views.filter.combine:
type: views.filter.string
label: 'Combine'
mapping:
fields:
type: sequence
label: 'Fields'
sequence:
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'
views.filter.in_operator:
type: views_filter
label: 'IN operator'
mapping:
operator:
type: string
label: 'Operator'
value:
type: sequence
label: 'Values'
sequence:
type: string
label: 'Value'
expose:
type: mapping
label: 'Expose'
mapping:
reduce:
type: boolean
label: 'Reduce'
views.filter.string:
type: views_filter
label: 'String'
mapping:
expose:
type: mapping
label: 'Exposed'
mapping:
required:
type: boolean
label: 'Required'
value:
type: string
label: 'Value'
views.filter_value.numeric:
type: mapping
label: 'Numeric'
mapping:
min:
type: string
label: 'Min'
max:
type: string
label: 'And max'
value:
type: string
label: 'Value'
views.filter_value.equality:
type: views.filter_value.numeric
label: 'Equality'
views.filter.many_to_one:
type: views.filter.in_operator
label: 'Many to one'
mapping:
reduce_duplicates:
type: boolean
label: 'Reduce duplicate'
views.filter.standard:
type: views_filter
label: 'Standard'
views.filter.group_item.*:
type: views_filter_group_item
label: 'Default'
views.filter.group_item.numeric:
type: views_filter_group_item
label: 'Group items'
mapping:
value:
type: views.filter_value.numeric
# Schema for the views filter value.
views.filter_value.boolean:
type: string
views.filter_value.combine:
type: string
views.filter.language:
type: views.filter.in_operator
label: 'Language'

View file

@ -0,0 +1,35 @@
# Schema for the views pager plugins.
views.pager.*:
type: views_pager
label: 'Default pager'
views.pager.none:
type: views_pager
label: 'Display all items'
views.pager.some:
type: views_pager
label: 'Display a specified number of items'
views.pager.mini:
type: views_pager_sql
label: 'Paged output, mini pager'
views.pager.full:
type: views_pager_sql
label: 'Paged output, full pager'
mapping:
tags:
type: mapping
label: 'Tags'
mapping:
first:
type: label
label: 'First page link text'
last:
type: label
label: 'Last page link text'
quantity:
type: integer
label: 'Number of pager links visible'

View file

@ -0,0 +1,24 @@
# Schema for the views query.
views.query.views_query:
type: views_query
label: 'Views query'
mapping:
query_comment:
type: string
label: 'Query comment'
disable_sql_rewrite:
type: boolean
label: 'Disable SQL rewriting'
distinct:
type: boolean
label: 'Distinct'
replica:
type: boolean
label: 'Use Replica Server'
query_tags:
type: sequence
label: 'Query Tags'
sequence:
type: string
label: 'Tag'

View file

@ -0,0 +1,33 @@
# Schema for the views relationship.
views.relationship.*:
type: views_relationship
label: 'Standard'
views.relationship.standard:
type: views_relationship
label: 'Standard'
views.relationship.broken:
type: views_relationship
label: 'Broken'
views.relationship.groupwise_max:
type: views_relationship
label: 'Groupwise max'
mapping:
subquery_sort:
type: string
label: 'Representative sort criteria'
subquery_order:
type: string
label: 'Representative sort order'
subquery_regenerate:
type: boolean
label: 'Generate subquery each time view is run'
subquery_view:
type: string
label: 'Representative view'
subquery_namespace:
type: string
label: 'Subquery namespace'

View file

@ -0,0 +1,91 @@
# Schema for the views row.
views.row.*:
type: views_row
views.row.entity:*:
type: views_entity_row
label: 'Entity options'
views.row.fields:
type: views_row
label: 'Field options'
mapping:
default_field_elements:
type: boolean
label: 'Provide default field wrapper elements'
inline:
type: sequence
label: 'Inline'
sequence:
type: string
label: 'Inline'
separator:
type: string
label: 'Separator'
hide_empty:
type: boolean
label: 'Hide empty'
views.row.rss_fields:
type: views_row
label: 'RSS field options'
mapping:
title_field:
type: string
label: 'Title field'
link_field:
type: string
label: 'Link field'
description_field:
type: string
label: 'Description field'
creator_field:
type: string
label: 'Creator field'
date_field:
type: string
label: 'Publication date field'
guid_field_options:
type: mapping
label: 'Guid settings'
mapping:
guid_field:
type: string
label: 'GUID field'
guid_field_is_permalink:
type: boolean
label: 'GUID is permalink'
views.row.opml_fields:
type: views_row
label: 'OPML field options'
mapping:
type_field:
type: string
label: 'Type attribute'
text_field:
type: string
label: 'Text attribute'
created_field:
type: string
label: 'Created attribute'
description_field:
type: string
label: 'Description attribute'
html_url_field:
type: string
label: 'HTML URL attribute'
language_field:
type: string
label: 'Language attribute'
xml_url_field:
type: string
label: 'XML URL attribute'
url_field:
type: string
label: 'URL attribute'
views.row.entity_reference:
type: views.row.fields
label: 'Entity Reference inline fields'

View file

@ -0,0 +1,153 @@
# Schema for the configuration files of the Views module.
views.settings:
type: config_object
label: 'Views settings'
mapping:
display_extenders:
type: sequence
label: 'Display extenders'
sequence:
type: string
label: 'Display extender'
skip_cache:
type: boolean
label: 'Disable views data caching'
sql_signature:
type: boolean
label: 'Add Views signature to all SQL queries'
ui:
type: mapping
label: 'UI settings'
mapping:
show:
type: mapping
label: 'Live preview settings'
mapping:
additional_queries:
type: boolean
label: 'Show other queries run during render during live preview'
advanced_column:
type: boolean
label: 'Always show advanced display settings'
master_display:
type: boolean
label: 'Always show the master (default) display'
performance_statistics:
type: boolean
label: 'Show performance statistics'
preview_information:
type: boolean
label: 'Show information and statistics about the view during live preview'
sql_query:
type: mapping
label: 'Query settings'
mapping:
enabled:
type: boolean
label: 'Show the SQL query'
where:
type: string
label: 'Show SQL query'
display_embed:
type: boolean
label: 'Allow embedded displays'
always_live_preview:
type: boolean
label: 'Automatically update preview on changes'
exposed_filter_any_label:
type: string
label: 'Label for "Any" value on non-required single-select exposed filters'
field_rewrite_elements:
type: sequence
label: 'Field rewrite elements'
sequence:
type: string
label: 'Element'
views.view.*:
type: config_entity
label: 'View'
mapping:
id:
type: string
label: 'ID'
label:
type: label
label: 'Label'
module:
type: string
label: 'Module'
description:
type: text
label: 'Administrative description'
tag:
type: string
label: 'Tag'
base_table:
type: string
label: 'Base table'
base_field:
type: string
label: 'Base field'
core:
type: string
label: 'Drupal version'
display:
type: sequence
label: 'Displays'
sequence:
type: mapping
label: 'Display settings'
mapping:
id:
type: string
label: 'Machine name'
display_title:
type: text
label: 'Title'
display_plugin:
type: string
label: 'Display plugin'
position:
type: integer
label: 'Position'
display_options:
type: views.display.[%parent.display_plugin]
cache_metadata:
type: mapping
label: 'Cache metadata'
mapping:
max-age:
type: integer
label: 'Cache maximum age'
contexts:
type: sequence
label: 'Cache contexts'
sequence:
type: string
tags:
type: sequence
label: 'Cache tags'
sequence:
type: string
# Deprecated.
cacheable:
type: boolean
label: 'Cacheable'
views_block:
type: block_settings
label: 'View block'
mapping:
views_label:
type: label
lable: 'Title'
items_per_page:
type: string
label: 'Items per block'
block.settings.views_block:*:
type: views_block
block.settings.views_exposed_filter_block:*:
type: views_block

View file

@ -0,0 +1,51 @@
# Schema for the views sort plugins.
views.sort.*:
type: views_sort
label: 'Default sort'
views.sort.boolean:
type: views_sort
label: 'Boolean sort'
views.sort.date:
type: views_sort
label: 'Date sort'
mapping:
granularity:
type: string
label: 'Granularity'
views.sort.broken:
type: views_sort
label: 'Broken'
views.sort.random:
type: views_sort
label: 'Random'
views.sort.standard:
type: views_sort
label: 'Standard'
# Schema for the views sort expose.
views.sort_expose.*:
type: views_sort_expose
label: 'Fallback sort expose settings'
views.sort_expose.boolean:
type: views_sort_expose
label: 'Boolean sort expose settings'
views.sort_expose.date:
type: views_sort_expose
label: 'Date sort expose settings'
views.sort_expose.standard:
type: views_sort_expose
label: 'Standard sort expose settings'
views.sort_expose.random:
type: views.sort_expose.standard
label: 'Random sort expose settings'

View file

@ -0,0 +1,156 @@
# Schema for the views style plugins.
views.style.*:
type: views_style
label: 'Default style'
views.style.default:
type: views_style
label: 'Unformatted list'
views.style.html_list:
type: views_style
label: 'HTML List'
mapping:
type:
type: string
label: 'List type'
wrapper_class:
type: string
label: 'Wrapper class'
class:
type: string
label: 'List class'
views.style.grid:
type: views_style
label: 'Grid'
mapping:
columns:
type: integer
label: 'Number of columns'
automatic_width:
type: boolean
label: 'Automatic width'
alignment:
type: string
label: 'Alignment'
row_class_custom:
type: string
label: 'Custom row classes'
row_class_default:
type: boolean
label: 'Default views row classes'
col_class_custom:
type: string
label: 'Custom column classes'
col_class_default:
type: boolean
label: 'Default views column classes'
views.style.table:
type: views_style
label: 'Table'
mapping:
columns:
type: sequence
label: 'Columns'
sequence:
type: string
label: 'Columns name'
default:
type: string
label: 'Default sort'
info:
type: sequence
label: 'Columns info'
sequence:
type: mapping
label: 'Column info'
mapping:
sortable:
type: boolean
label: 'Sortable'
default_sort_order:
type: string
label: 'Default order'
align:
type: string
label: 'Align'
separator:
type: string
label: 'Separator'
empty_column:
type: boolean
label: 'Hide empty columns'
responsive:
type: string
label: 'Responsive'
override:
type: boolean
label: 'Override normal sorting if click sorting is used'
sticky:
type: boolean
label: 'Enable Drupal style "sticky" table headers (Javascript)'
summary:
type: label
label: 'Summary title'
order:
type: string
label: 'Default order'
empty_table:
type: boolean
label: 'Show the empty text in the table'
caption:
type: label
label: 'Caption for the table'
description:
type: text
label: 'Table description'
views.style.default_summary:
type: views_style
label: 'Summary options'
mapping:
base_path:
type: string
label: 'Base path'
count:
type: boolean
label: 'Display record count with link'
override:
type: boolean
label: 'Override number of items to display'
items_per_page:
type: integer
label: 'Items to display'
views.style.rss:
type: views_style
label: 'RSS Feed'
mapping:
description:
type: label
label: 'RSS description'
views.style.unformatted_summary:
type: views.style.default_summary
label: 'Unformatted'
mapping:
inline:
type: boolean
label: 'Display items inline'
separator:
type: string
label: 'Separator'
views.style.entity_reference:
type: views_style
label: 'Entity Reference list'
mapping:
search_fields:
type: sequence
label: 'Search fields'
sequence:
type: string
label: 'Search field'

View file

@ -0,0 +1,19 @@
/* table style column align */
.views-align-left {
text-align: left;
}
.views-align-right {
text-align: right;
}
.views-align-center {
text-align: center;
}
/* Grid style column align. */
.views-view-grid .views-col {
float: left;
}
.views-view-grid .views-row {
clear: both;
float: left;
width: 100%;
}

View file

@ -0,0 +1,207 @@
/**
* @file
* Handles AJAX fetching of views, including filter submission and response.
*/
(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]);
}
}
}
};
/**
* @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;
// 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];
}
// 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;
}
}
this.element_settings = {
url: ajax_path + queryString,
submit: settings,
setClick: true,
event: 'click',
selector: 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
var self_settings = $.extend({}, this.element_settings, {
event: 'RefreshView',
base: this.selector,
element: this.$view.get(0)
});
this.refreshViewAjax = Drupal.ajax(self_settings);
};
/**
* @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, {
base: $(this).attr('id'),
element: this
});
that.exposedFormAjax[index] = Drupal.ajax(self_settings);
});
};
/**
* @return {bool}
* If there is at least one parent with a view class return false.
*
* @todo remove .size() replace with .length.
*/
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').size();
};
/**
* 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) {
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, {
submit: viewData,
base: false,
element: link
});
this.pagerAjax = Drupal.ajax(self_settings);
};
/**
* 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);
}
};
})(jQuery, Drupal, drupalSettings);

View file

@ -0,0 +1,110 @@
/**
* @file
* Some basic behaviors and utility functions for Views.
*/
(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 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, ' '));
}
}
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;
}
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);
}
var chars = ['#', '?', '&'];
for (var 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

@ -0,0 +1,41 @@
<?php
namespace Drupal\views\Ajax;
use Drupal\Core\Ajax\CommandInterface;
/**
* Provides an AJAX command for highlighting a certain new piece of html.
*
* This command is implemented in Drupal.AjaxCommands.prototype.viewsHighlight.
*/
class HighlightCommand implements CommandInterface {
/**
* A CSS selector string.
*
* @var string
*/
protected $selector;
/**
* Constructs a \Drupal\views\Ajax\HighlightCommand object.
*
* @param string $selector
* A CSS selector.
*/
public function __construct($selector) {
$this->selector = $selector;
}
/**
* {@inheritdoc}
*/
public function render() {
return array(
'command' => 'viewsHighlight',
'selector' => $this->selector,
);
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\views\Ajax;
use Drupal\Core\Ajax\CommandInterface;
/**
* Provides an AJAX command for replacing the page title.
*
* This command is implemented in Drupal.AjaxCommands.prototype.viewsReplaceTitle.
*/
class ReplaceTitleCommand implements CommandInterface {
/**
* The page title to replace.
*
* @var string
*/
protected $title;
/**
* Constructs a \Drupal\views\Ajax\ReplaceTitleCommand object.
*
* @param string $title
* The title of the page.
*/
public function __construct($title) {
$this->title = $title;
}
/**
* {@inheritdoc}
*/
public function render() {
return array(
'command' => 'viewsReplaceTitle',
'selector' => $this->title,
);
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\views\Ajax;
use Drupal\Core\Ajax\CommandInterface;
/**
* Provides an AJAX command for scrolling to the top of an element.
*
* This command is implemented in Drupal.AjaxCommands.prototype.viewsScrollTop.
*/
class ScrollTopCommand implements CommandInterface {
/**
* A CSS selector string.
*
* @var string
*/
protected $selector;
/**
* Constructs a \Drupal\views\Ajax\ScrollTopCommand object.
*
* @param string $selector
* A CSS selector.
*/
public function __construct($selector) {
$this->selector = $selector;
}
/**
* {@inheritdoc}
*/
public function render() {
return array(
'command' => 'viewsScrollTop',
'selector' => $this->selector,
);
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\views\Ajax;
use Drupal\Core\Ajax\CommandInterface;
/**
* Provides an AJAX command for showing the save and cancel buttons.
*
* This command is implemented in Drupal.AjaxCommands.prototype.viewsShowButtons.
*/
class ShowButtonsCommand implements CommandInterface {
/**
* Whether the view has been changed.
*
* @var bool
*/
protected $changed;
/**
* Constructs a \Drupal\views\Ajax\ShowButtonsCommand object.
*
* @param bool $changed
* Whether the view has been changed.
*/
public function __construct($changed) {
$this->changed = $changed;
}
/**
* {@inheritdoc}
*/
public function render() {
return array(
'command' => 'viewsShowButtons',
'changed' => $this->changed,
);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Drupal\views\Ajax;
use Drupal\Core\Ajax\CommandInterface;
/**
* Provides an AJAX command for triggering the views live preview.
*
* This command is implemented in Drupal.AjaxCommands.prototype.viewsTriggerPreview.
*/
class TriggerPreviewCommand implements CommandInterface {
/**
* {@inheritdoc}
*/
public function render() {
return array(
'command' => 'viewsTriggerPreview',
);
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace Drupal\views\Ajax;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\views\ViewExecutable;
/**
* Custom JSON response object for an ajax view response.
*
* We use a special response object to be able to fire a proper alter hook.
*/
class ViewAjaxResponse extends AjaxResponse {
/**
* The view executed on this ajax request.
*
* @var \Drupal\views\ViewExecutable
*/
protected $view;
/**
* Sets the executed view of this response.
*
* @param \Drupal\views\ViewExecutable $view
* The View executed on this ajax request.
*/
public function setView(ViewExecutable $view) {
$this->view = $view;
}
/**
* Gets the executed view of this response.
*
* @return \Drupal\views\ViewExecutable $view
* The View executed on this ajax request.
*/
public function getView() {
return $this->view;
}
}

View file

@ -0,0 +1,122 @@
<?php
namespace Drupal\views;
use Drupal\Core\Extension\ModuleHandlerInterface;
/**
* This tool is a small plugin manager to perform analysis on a view and
* report results to the user. This tool is meant to let modules that
* provide data to Views also help users properly use that data by
* detecting invalid configurations. Views itself comes with only a
* small amount of analysis tools, but more could easily be added either
* by modules or as patches to Views itself.
*/
class Analyzer {
/**
* A module handler that invokes the 'views_analyze' hook.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* Constructs an Analyzer object.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler that invokes the 'views_analyze' hook.
*/
public function __construct(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* Analyzes a review and return the results.
*
* @param \Drupal\views\ViewExecutable $view
* The view to analyze.
*
* @return array
* An array of analyze results organized into arrays keyed by 'ok',
* 'warning' and 'error'.
*/
public function getMessages(ViewExecutable $view) {
$view->initDisplay();
$messages = $this->moduleHandler->invokeAll('views_analyze', array($view));
return $messages;
}
/**
* Formats the analyze result into a message string.
*
* This is based upon the format of drupal_set_message which uses separate
* boxes for "ok", "warning" and "error".
*/
public function formatMessages(array $messages) {
if (empty($messages)) {
$messages = array(static::formatMessage(t('View analysis can find nothing to report.'), 'ok'));
}
$types = array('ok' => array(), 'warning' => array(), 'error' => array());
foreach ($messages as $message) {
if (empty($types[$message['type']])) {
$types[$message['type']] = array();
}
$types[$message['type']][] = $message['message'];
}
$output = '';
foreach ($types as $type => $messages) {
$type .= ' messages';
$message = '';
if (count($messages) > 1) {
$item_list = array(
'#theme' => 'item_list',
'#items' => $messages,
);
$message = drupal_render($item_list);
}
elseif ($messages) {
$message = array_shift($messages);
}
if ($message) {
$output .= "<div class=\"$type\">$message</div>";
}
}
return $output;
}
/**
* Formats an analysis message.
*
* This tool should be called by any module responding to the analyze hook
* to properly format the message. It is usually used in the form:
* @code
* $ret[] = Analyzer::formatMessage(t('This is the message'), 'ok');
* @endcode
*
* The 'ok' status should be used to provide information about things
* that are acceptable. In general analysis isn't interested in 'ok'
* messages, but instead the 'warning', which is a category for items
* that may be broken unless the user knows what he or she is doing,
* and 'error' for items that are definitely broken are much more useful.
*
* @param string $message
* @param string $type
* The type of message. This should be "ok", "warning" or "error". Other
* values can be used but how they are treated by the output routine
* is undefined.
*
* @return array
* A single formatted message, consisting of a key message and a key type.
*/
static function formatMessage($message, $type = 'error') {
return array('message' => $message, 'type' => $type);
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views access plugins.
*
* @see \Drupal\views\Plugin\views\access\AccessPluginBase
*
* @ingroup views_access_plugins
*
* @Annotation
*/
class ViewsAccess extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* A short help string; this is displayed in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $help = '';
/**
* The types of the display this plugin can be used with.
*
* For example the Feed display defines the type 'feed', so only rss style
* and row plugins can be used in the views UI.
*
* @var array
*/
public $display_types;
/**
* The base tables on which this access plugin can be used.
*
* If no base table is specified the plugin can be used with all tables.
*
* @var array
*/
public $base;
/**
* Whether the plugin should be not selectable in the UI.
*
* If set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,16 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views area handlers.
*
* @see \Drupal\views\Plugin\views\area\AreaPluginBase
*
* @ingroup views_area_handlers
*
* @Annotation
*/
class ViewsArea extends ViewsHandlerAnnotationBase {
}

View file

@ -0,0 +1,16 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views argument handlers.
*
* @see \Drupal\views\Plugin\views\argument\ArgumentPluginBase
*
* @ingroup views_argument_handlers
*
* @Annotation
*/
class ViewsArgument extends ViewsHandlerAnnotationBase {
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views argument default plugins.
*
* @see \Drupal\views\Plugin\views\argument_default\ArgumentDefaultPluginBase
*
* @ingroup views_argument_default_plugins
*
* @Annotation
*/
class ViewsArgumentDefault extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* Whether the plugin should be not selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,50 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views argument validator plugins.
*
* @see \Drupal\views\Plugin\views\argument_validator\ArgumentValidatorPluginBase
*
* @ingroup views_argument_validate_plugins
*
* @Annotation
*/
class ViewsArgumentValidator extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* Whether the plugin should be not selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,78 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views cache plugins.
*
* @see \Drupal\views\Plugin\views\cache\CachePluginBase
*
* @ingroup views_cache_plugins
*
* @Annotation
*/
class ViewsCache extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* A short help string; this is displayed in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $help = '';
/**
* The types of the display this plugin can be used with.
*
* For example the Feed display defines the type 'feed', so only rss style
* and row plugins can be used in the views UI.
*
* @var array
*/
public $display_types;
/**
* The base tables on which this cache plugin can be used.
*
* If no base table is specified the plugin can be used with all tables.
*
* @var array
*/
public $base;
/**
* Whether the plugin should be not selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,134 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views display plugins.
*
* @see \Drupal\views\Plugin\views\display\DisplayPluginBase
*
* @ingroup views_display_plugins
*
* @Annotation
*/
class ViewsDisplay extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* The administrative name of the display.
*
* The name is displayed on the Views overview and also used as default name
* for new displays.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $admin = '';
/**
* A short help string; this is displayed in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $help = '';
/**
* Whether or not to use hook_menu() to register a route.
*
* @var bool
*/
public $uses_menu_links;
/**
* Does the display plugin registers routes to the route.
*
* @var bool
*/
public $uses_route;
/**
* Does the display plugin provide blocks.
*
* @var bool
*/
public $uses_hook_block;
/**
* A list of places where contextual links should be added.
* For example:
* @code
* array(
* 'page',
* 'block',
* )
* @endcode
*
* If you don't specify it there will be contextual links rendered for all
* displays of a view. If this is not set or regions have been specified,
* views will display an option to 'hide contextual links'. Use an empty
* array to disable.
*
* @var array
*/
public $contextual_links_locations;
/**
* The base tables on which this display plugin can be used.
*
* If no base table is specified the plugin can be used with all tables.
*
* @var array
*/
public $base;
/**
* The theme function used to render the display's output.
*
* @return string
*/
public $theme;
/**
* Whether the plugin should be not selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
/**
* Whether the display returns a response object.
*
* @var bool
*/
public $returns_response;
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views display extender plugins.
*
* @see \Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase
*
* @ingroup views_display_extender_plugins
*
* @Annotation
*/
class ViewsDisplayExtender extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* A short help string; this is displayed in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $help = '';
/**
* Whether or not the plugin is selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,79 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views exposed form plugins.
*
* @see \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface
* @see \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase
*
* @ingroup views_exposed_form_plugins
*
* @Annotation
*/
class ViewsExposedForm extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* A short help string; this is displayed in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $help = '';
/**
* The types of the display this plugin can be used with.
*
* For example the Feed display defines the type 'feed', so only rss style
* and row plugins can be used in the views UI.
*
* @var array
*/
public $display_types;
/**
* The base tables on which this exposed form plugin can be used.
*
* If no base table is specified the plugin can be used with all tables.
*
* @var array
*/
public $base;
/**
* Whether the plugin should be not selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,16 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views field handlers.
*
* @see \Drupal\views\Plugin\views\field\FieldPluginBase
*
* @ingroup views_field_handlers
*
* @Annotation
*/
class ViewsField extends ViewsHandlerAnnotationBase {
}

View file

@ -0,0 +1,16 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views filter handlers.
*
* @see \Drupal\views\Plugin\views\filter\FilterPluginBase
*
* @ingroup views_filter_handlers
*
* @Annotation
*/
class ViewsFilter extends ViewsHandlerAnnotationBase {
}

View file

@ -0,0 +1,12 @@
<?php
namespace Drupal\views\Annotation;
use Drupal\Component\Annotation\PluginID;
/**
* Defines an abstract base class for all views handler annotations.
*/
abstract class ViewsHandlerAnnotationBase extends PluginID {
}

View file

@ -0,0 +1,16 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views join plugins.
*
* @see \Drupal\views\Plugin\views\join\JoinPluginBase
*
* @ingroup views_join_handlers
*
* @Annotation
*/
class ViewsJoin extends ViewsHandlerAnnotationBase {
}

View file

@ -0,0 +1,85 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views pager plugins.
*
* @see \Drupal\views\Plugin\views\pager\PagerPluginBase
*
* @ingroup views_pager_plugins
*
* @Annotation
*/
class ViewsPager extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* A short help string; this is displayed in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $help = '';
/**
* The theme function used to render the pager's output.
*
* @return string
*/
public $theme;
/**
* The types of the display this plugin can be used with.
*
* For example the Feed display defines the type 'feed', so only rss style
* and row plugins can be used in the views UI.
*
* @var array
*/
public $display_types;
/**
* The base tables on which this pager plugin can be used.
*
* If no base table is specified the plugin can be used with all tables.
*
* @var array
*/
public $base;
/**
* Whether the plugin should be not selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,20 @@
<?php
namespace Drupal\views\Annotation;
use Drupal\Component\Annotation\AnnotationInterface;
use Drupal\Component\Annotation\Plugin;
/**
* Defines an abstract base class for all views plugin annotations.
*/
abstract class ViewsPluginAnnotationBase extends Plugin implements AnnotationInterface {
/**
* Whether or not to register a theme function automatically.
*
* @var bool (optional)
*/
public $register_theme = TRUE;
}

View file

@ -0,0 +1,59 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views query plugins.
*
* @see \Drupal\views\Plugin\views\query\QueryPluginBase
*
* @ingroup views_query_plugins
*
* @Annotation
*/
class ViewsQuery extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* A short help string; this is displayed in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $help = '';
/**
* Whether the plugin should be not selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,16 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views relationship handlers.
*
* @see \Drupal\views\Plugin\views\relationship\RelationshipPluginBase
*
* @ingroup views_relationship_handlers
*
* @Annotation
*/
class ViewsRelationship extends ViewsHandlerAnnotationBase {
}

View file

@ -0,0 +1,83 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views row plugins.
*
* @see \Drupal\views\Plugin\views\row\RowPluginBase
*
* @ingroup views_row_plugins
*
* @Annotation
*/
class ViewsRow extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* A short help string; this is displayed in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $help = '';
/**
* The theme function used to render the row output.
*
* @return string
*/
public $theme;
/**
* The base tables on which this row plugin can be used.
*
* @var array
*/
public $base;
/**
* The types of the display this plugin can be used with.
*
* For example the Feed display defines the type 'feed', so only rss style
* and row plugins can be used in the views UI.
*
* @var array
*/
public $display_types;
/**
* Whether the plugin should be not selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,16 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views sort handlers.
*
* @see \Drupal\views\Plugin\views\sort\SortPluginBase
*
* @ingroup views_sort_handlers
*
* @Annotation
*/
class ViewsSort extends ViewsHandlerAnnotationBase {
}

View file

@ -0,0 +1,85 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views style plugins.
*
* @see \Drupal\views\Plugin\views\style\StylePluginBase
*
* @ingroup views_style_plugins
*
* @Annotation
*/
class ViewsStyle extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* A short help string; this is displayed in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $help = '';
/**
* The theme function used to render the style output.
*
* @var string
*/
public $theme;
/**
* The types of the display this plugin can be used with.
*
* For example the Feed display defines the type 'feed', so only rss style
* and row plugins can be used in the views UI.
*
* @var array
*/
public $display_types;
/**
* The base tables on which this style plugin can be used.
*
* If no base table is specified the plugin can be used with all tables.
*
* @var array
*/
public $base;
/**
* Whether the plugin should be not selectable in the UI.
*
* If it's set to TRUE, you can still use it via the API in config files.
*
* @var bool
*/
public $no_ui;
}

View file

@ -0,0 +1,49 @@
<?php
namespace Drupal\views\Annotation;
/**
* Defines a Plugin annotation object for views wizard plugins.
*
* @see \Drupal\views\Plugin\views\wizard\WizardPluginBase
* @see \Drupal\views\Plugin\views\wizard\WizardInterface
*
* @ingroup views_wizard_plugins
*
* @Annotation
*/
class ViewsWizard extends ViewsPluginAnnotationBase {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The plugin title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $title = '';
/**
* (optional) The short title used in the views UI.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $short_title = '';
/**
* The base tables on which this wizard is used.
*
* @var array
*/
public $base_table;
}

View file

@ -0,0 +1,206 @@
<?php
namespace Drupal\views\Controller;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\views\Ajax\ScrollTopCommand;
use Drupal\views\Ajax\ViewAjaxResponse;
use Drupal\views\ViewExecutableFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Defines a controller to load a view via AJAX.
*/
class ViewAjaxController implements ContainerInjectionInterface {
/**
* The entity storage for views.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* The factory to load a view executable with.
*
* @var \Drupal\views\ViewExecutableFactory
*/
protected $executableFactory;
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* The current path.
*
* @var \Drupal\Core\Path\CurrentPathStack
*/
protected $currentPath;
/**
* The redirect destination.
*
* @var \Drupal\Core\Routing\RedirectDestinationInterface
*/
protected $redirectDestination;
/**
* Constructs a ViewAjaxController object.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage for views.
* @param \Drupal\views\ViewExecutableFactory $executable_factory
* The factory to load a view executable with.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
* @param \Drupal\Core\Path\CurrentPathStack $current_path
* The current path.
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
* The redirect destination.
*/
public function __construct(EntityStorageInterface $storage, ViewExecutableFactory $executable_factory, RendererInterface $renderer, CurrentPathStack $current_path, RedirectDestinationInterface $redirect_destination) {
$this->storage = $storage;
$this->executableFactory = $executable_factory;
$this->renderer = $renderer;
$this->currentPath = $current_path;
$this->redirectDestination = $redirect_destination;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.manager')->getStorage('view'),
$container->get('views.executable'),
$container->get('renderer'),
$container->get('path.current'),
$container->get('redirect.destination')
);
}
/**
* Loads and renders a view via AJAX.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request object.
*
* @return \Drupal\views\Ajax\ViewAjaxResponse
* The view response as ajax response.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* Thrown when the view was not found.
*/
public function ajaxView(Request $request) {
$name = $request->request->get('view_name');
$display_id = $request->request->get('view_display_id');
if (isset($name) && isset($display_id)) {
$args = $request->request->get('view_args');
$args = isset($args) && $args !== '' ? explode('/', $args) : array();
// Arguments can be empty, make sure they are passed on as NULL so that
// argument validation is not triggered.
$args = array_map(function ($arg) {
return ($arg == '' ? NULL : $arg);
}, $args);
$path = $request->request->get('view_path');
$dom_id = $request->request->get('view_dom_id');
$dom_id = isset($dom_id) ? preg_replace('/[^a-zA-Z0-9_-]+/', '-', $dom_id) : NULL;
$pager_element = $request->request->get('pager_element');
$pager_element = isset($pager_element) ? intval($pager_element) : NULL;
$response = new ViewAjaxResponse();
// Remove all of this stuff from the query of the request so it doesn't
// end up in pagers and tablesort URLs.
foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER) as $key) {
$request->query->remove($key);
$request->request->remove($key);
}
// Load the view.
if (!$entity = $this->storage->load($name)) {
throw new NotFoundHttpException();
}
$view = $this->executableFactory->get($entity);
if ($view && $view->access($display_id)) {
$response->setView($view);
// Fix the current path for paging.
if (!empty($path)) {
$this->currentPath->setPath('/' . $path, $request);
}
// 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();
$query_all = $request->query->all();
$request->query->replace($request_all + $query_all);
// Overwrite the destination.
// @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;
}
$this->redirectDestination->set($origin_destination);
// Override the display's pager_element with the one actually used.
if (isset($pager_element)) {
$response->addCommand(new ScrollTopCommand(".js-view-dom-id-$dom_id"));
$view->displayHandlers->get($display_id)->setOption('pager_element', $pager_element);
}
// Reuse the same DOM id so it matches that in drupalSettings.
$view->dom_id = $dom_id;
$context = new RenderContext();
$preview = $this->renderer->executeInRenderContext($context, function() use ($view, $display_id, $args) {
return $view->preview($display_id, $args);
});
if (!$context->isEmpty()) {
$bubbleable_metadata = $context->pop();
BubbleableMetadata::createFromRenderArray($preview)
->merge($bubbleable_metadata)
->applyTo($preview);
}
$response->addCommand(new ReplaceCommand(".js-view-dom-id-$dom_id", $preview));
return $response;
}
else {
throw new AccessDeniedHttpException();
}
}
else {
throw new NotFoundHttpException();
}
}
}

View file

@ -0,0 +1,108 @@
<?php
namespace Drupal\views;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Plugin\DefaultLazyPluginCollection;
/**
* A class which wraps the displays of a view so you can lazy-initialize them.
*/
class DisplayPluginCollection extends DefaultLazyPluginCollection {
/**
* Stores a reference to the view which has this displays attached.
*
* @var \Drupal\views\ViewExecutable
*/
protected $view;
/**
* {@inheritdoc}
*/
protected $pluginKey = 'display_plugin';
/**
* Constructs a DisplayPluginCollection object.
*
* @param \Drupal\views\ViewExecutable $view
* The view which has this displays attached.
* @param \Drupal\Component\Plugin\PluginManagerInterface $manager
* The manager to be used for instantiating plugins.
*/
public function __construct(ViewExecutable $view, PluginManagerInterface $manager) {
parent::__construct($manager, $view->storage->get('display'));
$this->view = $view;
$this->initializePlugin('default');
}
/**
* Destructs a DisplayPluginCollection object.
*/
public function __destruct() {
$this->clear();
}
/**
* {@inheritdoc}
*
* @return \Drupal\views\Plugin\views\display\DisplayPluginBase
*/
public function &get($instance_id) {
return parent::get($instance_id);
}
/**
* {@inheritdoc}
*/
public function clear() {
foreach (array_filter($this->pluginInstances) as $display) {
$display->destroy();
}
parent::clear();
}
/**
* {@inheritdoc}
*/
protected function initializePlugin($display_id) {
// Retrieve and initialize the new display handler with data.
$display = &$this->view->storage->getDisplay($display_id);
try {
$this->configurations[$display_id] = $display;
parent::initializePlugin($display_id);
}
// Catch any plugin exceptions that are thrown. So we can fail nicely if a
// display plugin isn't found.
catch (PluginException $e) {
$message = $e->getMessage();
drupal_set_message(t('@message', array('@message' => $message)), 'warning');
}
// If no plugin instance has been created, return NULL.
if (empty($this->pluginInstances[$display_id])) {
return NULL;
}
$this->pluginInstances[$display_id]->initDisplay($this->view, $display);
// If this is not the default display handler, let it know which is since
// it may well use some data from the default.
if ($display_id != 'default') {
$this->pluginInstances[$display_id]->default_display = $this->pluginInstances['default'];
}
}
/**
* {@inheritdoc}
*/
public function remove($instance_id) {
$this->get($instance_id)->remove();
parent::remove($instance_id);
}
}

View file

@ -0,0 +1,102 @@
<?php
namespace Drupal\views\Element;
use Drupal\Core\Render\Element\RenderElement;
use Drupal\views\Views;
/**
* Provides a render element to display a view.
*
* @RenderElement("view")
*/
class View extends RenderElement {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#pre_render' => array(
array($class, 'preRenderViewElement'),
),
'#name' => NULL,
'#display_id' => 'default',
'#arguments' => array(),
'#embed' => TRUE,
'#cache' => [],
);
}
/**
* View element pre render callback.
*/
public static function preRenderViewElement($element) {
// Allow specific Views displays to explicitly perform pre-rendering, for
// those displays that need to be able to know the fully built render array.
if (!empty($element['#pre_rendered'])) {
return $element;
}
if (!isset($element['#view'])) {
$view = Views::getView($element['#name']);
}
else {
$view = $element['#view'];
}
$element += $view->element;
$view->element = &$element;
// Mark the element as being prerendered, so other code like
// \Drupal\views\ViewExecutable::setCurrentPage knows that its no longer
// possible to manipulate the $element.
$view->element['#pre_rendered'] = TRUE;
if (isset($element['#response'])) {
$view->setResponse($element['#response']);
}
if ($view && $view->access($element['#display_id'])) {
if (!empty($element['#embed'])) {
$element['view_build'] = $view->preview($element['#display_id'], $element['#arguments']);
}
else {
// Add contextual links to the view. We need to attach them to the dummy
// $view_array variable, since contextual_preprocess() requires that they
// be attached to an array (not an object) in order to process them. For
// our purposes, it doesn't matter what we attach them to, since once they
// are processed by contextual_preprocess() they will appear in the
// $title_suffix variable (which we will then render in
// views-view.html.twig).
$view->setDisplay($element['#display_id']);
// Add the result of the executed view as a child element so any
// #pre_render elements for the view will get processed. A #pre_render
// element cannot be added to the main element as this is already inside
// a #pre_render callback.
$element['view_build'] = $view->executeDisplay($element['#display_id'], $element['#arguments']);
if (isset($element['view_build']['#title'])) {
$element['#title'] = &$element['view_build']['#title'];
}
if (empty($view->display_handler->getPluginDefinition()['returns_response'])) {
// views_add_contextual_links() needs the following information in
// order to be attached to the view.
$element['#view_id'] = $view->storage->id();
$element['#view_display_show_admin_links'] = $view->getShowAdminLinks();
$element['#view_display_plugin_id'] = $view->display_handler->getPluginId();
views_add_contextual_links($element, 'view', $view->current_display);
}
}
if (empty($view->display_handler->getPluginDefinition()['returns_response'])) {
$element['#attributes']['class'][] = 'views-element-container';
$element['#theme_wrappers'] = array('container');
}
}
return $element;
}
}

View file

@ -0,0 +1,46 @@
<?php
namespace Drupal\views\Entity\Render;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
/**
* Renders entities in a configured language.
*/
class ConfigurableLanguageRenderer extends EntityTranslationRendererBase {
/**
* A specific language code for rendering if available.
*
* @var string|null
*/
protected $langcode;
/**
* Constructs a renderer object.
*
* @param \Drupal\views\ViewExecutable $view
* The entity row being rendered.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
* @param string|null $langcode
* A specific language code to set, if available.
*/
public function __construct(ViewExecutable $view, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type, $langcode) {
parent::__construct($view, $language_manager, $entity_type);
$this->langcode = $langcode;
}
/**
* {@inheritdoc}
*/
public function getLangcode(ResultRow $row) {
return $this->langcode;
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Drupal\views\Entity\Render;
use Drupal\views\ResultRow;
/**
* Renders entities in their default language.
*/
class DefaultLanguageRenderer extends EntityTranslationRendererBase {
/**
* {@inheritdoc}
*/
public function getLangcode(ResultRow $row) {
return $row->_entity->getUntranslated()->language()->getId();
}
}

View file

@ -0,0 +1,269 @@
<?php
namespace Drupal\views\Entity\Render;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\views\Plugin\views\field\Field;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
/**
* Renders entity fields.
*
* This is used to build render arrays for all entity field values of a view
* result set sharing the same relationship. An entity translation renderer is
* used internally to handle entity language properly.
*/
class EntityFieldRenderer extends RendererBase {
use EntityTranslationRenderTrait;
use DependencySerializationTrait;
/**
* The relationship being handled.
*
* @var string
*/
protected $relationship;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* A list of indexes of rows whose fields have already been rendered.
*
* @var int[]
*/
protected $processedRows = [];
/**
* Constructs an EntityFieldRenderer object.
*
* @param \Drupal\views\ViewExecutable $view
* The view whose fields are being rendered.
* @param string $relationship
* The relationship to be handled.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(ViewExecutable $view, $relationship, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type, EntityManagerInterface $entity_manager) {
parent::__construct($view, $language_manager, $entity_type);
$this->relationship = $relationship;
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return $this->getEntityTranslationRenderer()->getCacheContexts();
}
/**
* {@inheritdoc}
*/
public function getEntityTypeId() {
return $this->entityType->id();
}
/**
* {@inheritdoc}
*/
protected function getEntityManager() {
return $this->entityManager;
}
/**
* {@inheritdoc}
*/
protected function getLanguageManager() {
return $this->languageManager;
}
/**
* {@inheritdoc}
*/
protected function getView() {
return $this->view;
}
/**
* {@inheritdoc}
*/
public function query(QueryPluginBase $query, $relationship = NULL) {
$this->getEntityTranslationRenderer()->query($query, $relationship);
}
/**
* Renders entity field data.
*
* @param \Drupal\views\ResultRow $row
* A single row of the query result.
* @param \Drupal\views\Plugin\views\field\Field $field
* (optional) A field to be rendered.
*
* @return array
* A renderable array for the entity data contained in the result row.
*/
public function render(ResultRow $row, Field $field = NULL) {
// The method is called for each field in each result row. In order to
// leverage multiple-entity building of formatter output, we build the
// render arrays for all fields in all rows on the first call.
if (!isset($this->build)) {
$this->build = $this->buildFields($this->view->result);
}
if (isset($field)) {
$field_id = $field->options['id'];
// Pick the render array for the row / field we are being asked to render,
// and remove it from $this->build to free memory as we progress.
if (isset($this->build[$row->index][$field_id])) {
$build = $this->build[$row->index][$field_id];
unset($this->build[$row->index][$field_id]);
}
elseif (isset($this->build[$row->index])) {
// In the uncommon case where a field gets rendered several times
// (typically through direct Views API calls), the pre-computed render
// array was removed by the unset() above. We have to manually rebuild
// the render array for the row.
$build = $this->buildFields([$row])[$row->index][$field_id];
}
else {
// In case the relationship is optional, there might not be any fields
// to render for this row.
$build = [];
}
}
else {
// Same logic as above, in the case where we are being called for a whole
// row.
if (isset($this->build[$row->index])) {
$build = $this->build[$row->index];
unset($this->build[$row->index]);
}
else {
$build = $this->buildFields([$row])[$row->index];
}
}
return $build;
}
/**
* Builds the render arrays for all fields of all result rows.
*
* The output is built using EntityViewDisplay objects to leverage
* multiple-entity building and ensure a common code path with regular entity
* view.
* - Each relationship is handled by a separate EntityFieldRenderer instance,
* since it operates on its own set of entities. This also ensures different
* entity types are handled separately, as they imply different
* relationships.
* - Within each relationship, the fields to render are arranged in unique
* sets containing each field at most once (an EntityViewDisplay can
* only process a field once with given display options, but a View can
* contain the same field several times with different display options).
* - For each set of fields, entities are processed by bundle, so that
* formatters can operate on the proper field definition for the bundle.
*
* @param \Drupal\views\ResultRow[] $values
* An array of all ResultRow objects returned from the query.
*
* @return array
* A renderable array for the fields handled by this renderer.
*
* @see \Drupal\Core\Entity\Entity\EntityViewDisplay
*/
protected function buildFields(array $values) {
$build = [];
if ($values && ($field_ids = $this->getRenderableFieldIds())) {
$entity_type_id = $this->getEntityTypeId();
// Collect the entities for the relationship, fetch the right translation,
// and group by bundle. For each result row, the corresponding entity can
// be obtained from any of the fields handlers, so we arbitrarily use the
// first one.
$entities_by_bundles = [];
$field = $this->view->field[current($field_ids)];
foreach ($values as $result_row) {
if ($entity = $field->getEntity($result_row)) {
$entities_by_bundles[$entity->bundle()][$result_row->index] = $this->getEntityTranslation($entity, $result_row);
}
}
// Determine unique sets of fields that can be processed by the same
// display. Fields that appear several times in the View open additional
// "overflow" displays.
$display_sets = [];
foreach ($field_ids as $field_id) {
$field = $this->view->field[$field_id];
$field_name = $field->definition['field_name'];
$index = 0;
while (isset($display_sets[$index]['field_names'][$field_name])) {
$index++;
}
$display_sets[$index]['field_names'][$field_name] = $field;
$display_sets[$index]['field_ids'][$field_id] = $field;
}
// For each set of fields, build the output by bundle.
foreach ($display_sets as $display_fields) {
foreach ($entities_by_bundles as $bundle => $bundle_entities) {
// Create the display, and configure the field display options.
$display = EntityViewDisplay::create([
'targetEntityType' => $entity_type_id,
'bundle' => $bundle,
'status' => TRUE,
]);
foreach ($display_fields['field_ids'] as $field) {
$display->setComponent($field->definition['field_name'], [
'type' => $field->options['type'],
'settings' => $field->options['settings'],
]);
}
// Let the display build the render array for the entities.
$display_build = $display->buildMultiple($bundle_entities);
// Collect the field render arrays and index them using our internal
// row indexes and field IDs.
foreach ($display_build as $row_index => $entity_build) {
foreach ($display_fields['field_ids'] as $field_id => $field) {
$build[$row_index][$field_id] = !empty($entity_build[$field->definition['field_name']]) ? $entity_build[$field->definition['field_name']] : [];
}
}
}
}
}
return $build;
}
/**
* Returns a list of names of entity fields to be rendered.
*
* @return string[]
* An associative array of views fields.
*/
protected function getRenderableFieldIds() {
$field_ids = [];
foreach ($this->view->field as $field_id => $field) {
if ($field instanceof Field && $field->relationship == $this->relationship) {
$field_ids[] = $field_id;
}
}
return $field_ids;
}
}

View file

@ -0,0 +1,114 @@
<?php
namespace Drupal\views\Entity\Render;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\TranslatableInterface;
use Drupal\views\Plugin\views\PluginBase;
use Drupal\views\ResultRow;
/**
* Trait used to instantiate the view's entity translation renderer.
*/
trait EntityTranslationRenderTrait {
/**
* The renderer to be used to render the entity row.
*
* @var \Drupal\views\Entity\Render\EntityTranslationRendererBase
*/
protected $entityTranslationRenderer;
/**
* Returns the current renderer.
*
* @return \Drupal\views\Entity\Render\EntityTranslationRendererBase
* The configured renderer.
*/
protected function getEntityTranslationRenderer() {
if (!isset($this->entityTranslationRenderer)) {
$view = $this->getView();
$rendering_language = $view->display_handler->getOption('rendering_language');
$langcode = NULL;
$dynamic_renderers = array(
'***LANGUAGE_entity_translation***' => 'TranslationLanguageRenderer',
'***LANGUAGE_entity_default***' => 'DefaultLanguageRenderer',
);
if (isset($dynamic_renderers[$rendering_language])) {
// Dynamic language set based on result rows or instance defaults.
$renderer = $dynamic_renderers[$rendering_language];
}
else {
if (strpos($rendering_language, '***LANGUAGE_') !== FALSE) {
$langcode = PluginBase::queryLanguageSubstitutions()[$rendering_language];
}
else {
// Specific langcode set.
$langcode = $rendering_language;
}
$renderer = 'ConfigurableLanguageRenderer';
}
$class = '\Drupal\views\Entity\Render\\' . $renderer;
$entity_type = $this->getEntityManager()->getDefinition($this->getEntityTypeId());
$this->entityTranslationRenderer = new $class($view, $this->getLanguageManager(), $entity_type, $langcode);
}
return $this->entityTranslationRenderer;
}
/**
* Returns the entity translation matching the configured row language.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity object the field value being processed is attached to.
* @param \Drupal\views\ResultRow $row
* The result row the field value being processed belongs to.
*
* @return \Drupal\Core\Entity\FieldableEntityInterface
* The entity translation object for the specified row.
*/
public function getEntityTranslation(EntityInterface $entity, ResultRow $row) {
// We assume the same language should be used for all entity fields
// belonging to a single row, even if they are attached to different entity
// types. Below we apply language fallback to ensure a valid value is always
// picked.
$translation = $entity;
if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {
$langcode = $this->getEntityTranslationRenderer()->getLangcode($row);
$translation = $this->getEntityManager()->getTranslationFromContext($entity, $langcode);
}
return $translation;
}
/**
* Returns the entity type identifier.
*
* @return string
* The entity type identifier.
*/
abstract public function getEntityTypeId();
/**
* Returns the entity manager.
*
* @return \Drupal\Core\Entity\EntityManagerInterface
* The entity manager.
*/
abstract protected function getEntityManager();
/**
* Returns the language manager.
*
* @return \Drupal\Core\Language\LanguageManagerInterface
* The language manager.
*/
abstract protected function getLanguageManager();
/**
* Returns the top object of a view.
*
* @return \Drupal\views\ViewExecutable
* The view object.
*/
abstract protected function getView();
}

View file

@ -0,0 +1,54 @@
<?php
namespace Drupal\views\Entity\Render;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ResultRow;
/**
* Defines a base class for entity translation renderers.
*/
abstract class EntityTranslationRendererBase extends RendererBase {
/**
* Returns the language code associated with the given row.
*
* @param \Drupal\views\ResultRow $row
* The result row.
*
* @return string
* A language code.
*/
abstract public function getLangcode(ResultRow $row);
/**
* {@inheritdoc}
*/
public function query(QueryPluginBase $query, $relationship = NULL) {
}
/**
* {@inheritdoc}
*/
public function preRender(array $result) {
$view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
/** @var \Drupal\views\ResultRow $row */
foreach ($result as $row) {
// @todo Take relationships into account.
// See https://www.drupal.org/node/2457999.
$entity = $row->_entity;
$entity->view = $this->view;
$this->build[$entity->id()] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $this->getLangcode($row));
}
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $row) {
$entity_id = $row->_entity->id();
return $this->build[$entity_id];
}
}

View file

@ -0,0 +1,113 @@
<?php
namespace Drupal\views\Entity\Render;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
/**
* Defines a base class for entity renderers.
*/
abstract class RendererBase implements CacheableDependencyInterface {
/**
* The view executable wrapping the view storage entity.
*
* @var \Drupal\views\ViewExecutable
*/
public $view;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* The type of the entity being rendered.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* Contains an array of render arrays, one for each rendered entity.
*
* @var array
*/
protected $build;
/**
* Constructs a renderer object.
*
* @param \Drupal\views\ViewExecutable $view
* The entity row being rendered.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*/
public function __construct(ViewExecutable $view, LanguageManagerInterface $language_manager, EntityTypeInterface $entity_type) {
$this->view = $view;
$this->languageManager = $language_manager;
$this->entityType = $entity_type;
}
/**
* {@inheritdoc}
*/
public function getCacheMaxAge() {
return Cache::PERMANENT;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return [];
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return [];
}
/**
* Alters the query if needed.
*
* @param \Drupal\views\Plugin\views\query\QueryPluginBase $query
* The query to alter.
* @param string $relationship
* (optional) The relationship, used by a field.
*/
abstract public function query(QueryPluginBase $query, $relationship = NULL);
/**
* Runs before each entity is rendered.
*
* @param $result
* The full array of results from the query.
*/
public function preRender(array $result) {
}
/**
* Renders entity data.
*
* @param \Drupal\views\ResultRow $row
* A single row of the query result.
*
* @return array
* A renderable array for the entity data contained in the result row.
*/
abstract public function render(ResultRow $row);
}

View file

@ -0,0 +1,78 @@
<?php
namespace Drupal\views\Entity\Render;
use Drupal\Core\Language\LanguageInterface;
use Drupal\views\Plugin\views\query\QueryPluginBase;
use Drupal\views\ResultRow;
/**
* Renders entity translations in their row language.
*/
class TranslationLanguageRenderer extends EntityTranslationRendererBase {
/**
* Stores the field alias of the langcode column.
*
* @var string
*/
protected $langcodeAlias;
/**
* {@inheritdoc}
*/
public function query(QueryPluginBase $query, $relationship = NULL) {
// In order to render in the translation language of the entity, we need
// to add the language code of the entity to the query. Skip if the site
// is not multilingual or the entity is not translatable.
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);
$this->langcodeAlias = $query->addField($table_alias, $langcode_key);
}
}
/**
* {@inheritdoc}
*/
public function preRender(array $result) {
$view_builder = $this->view->rowPlugin->entityManager->getViewBuilder($this->entityType->id());
/** @var \Drupal\views\ResultRow $row */
foreach ($result as $row) {
$entity = $row->_entity;
$entity->view = $this->view;
$langcode = $this->getLangcode($row);
$this->build[$entity->id()][$langcode] = $view_builder->view($entity, $this->view->rowPlugin->options['view_mode'], $this->getLangcode($row));
}
}
/**
* {@inheritdoc}
*/
public function render(ResultRow $row) {
$entity_id = $row->_entity->id();
$langcode = $this->getLangcode($row);
return $this->build[$entity_id][$langcode];
}
/**
* {@inheritdoc}
*/
public function getLangcode(ResultRow $row) {
return isset($row->{$this->langcodeAlias}) ? $row->{$this->langcodeAlias} : $this->languageManager->getDefaultLanguage()->getId();
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['languages:' . LanguageInterface::TYPE_CONTENT];
}
}

View file

@ -0,0 +1,471 @@
<?php
namespace Drupal\views\Entity;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\views\Views;
use Drupal\views\ViewEntityInterface;
/**
* Defines a View configuration entity class.
*
* @ConfigEntityType(
* id = "view",
* label = @Translation("View", context = "View entity type"),
* handlers = {
* "access" = "Drupal\views\ViewAccessControlHandler"
* },
* admin_permission = "administer views",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "status" = "status"
* },
* config_export = {
* "id",
* "label",
* "module",
* "description",
* "tag",
* "base_table",
* "base_field",
* "core",
* "display",
* }
* )
*/
class View extends ConfigEntityBase implements ViewEntityInterface {
/**
* The name of the base table this view will use.
*
* @var string
*/
protected $base_table = 'node';
/**
* The unique ID of the view.
*
* @var string
*/
protected $id = NULL;
/**
* The label of the view.
*/
protected $label;
/**
* The description of the view, which is used only in the interface.
*
* @var string
*/
protected $description = '';
/**
* The "tags" of a view.
*
* The tags are stored as a single string, though it is used as multiple tags
* for example in the views overview.
*
* @var string
*/
protected $tag = '';
/**
* The core version the view was created for.
*
* @var string
*/
protected $core = \Drupal::CORE_COMPATIBILITY;
/**
* Stores all display handlers of this view.
*
* An array containing Drupal\views\Plugin\views\display\DisplayPluginBase
* objects.
*
* @var array
*/
protected $display = array();
/**
* The name of the base field to use.
*
* @var string
*/
protected $base_field = 'nid';
/**
* Stores a reference to the executable version of this view.
*
* @var \Drupal\views\ViewExecutable
*/
protected $executable;
/**
* The module implementing this view.
*
* @var string
*/
protected $module = 'views';
/**
* {@inheritdoc}
*/
public function getExecutable() {
// Ensure that an executable View is available.
if (!isset($this->executable)) {
$this->executable = Views::executableFactory()->get($this);
}
return $this->executable;
}
/**
* {@inheritdoc}
*/
public function createDuplicate() {
$duplicate = parent::createDuplicate();
unset($duplicate->executable);
return $duplicate;
}
/**
* {@inheritdoc}
*/
public function label() {
if (!$label = $this->get('label')) {
$label = $this->id();
}
return $label;
}
/**
* {@inheritdoc}
*/
public function addDisplay($plugin_id = 'page', $title = NULL, $id = NULL) {
if (empty($plugin_id)) {
return FALSE;
}
$plugin = Views::pluginManager('display')->getDefinition($plugin_id);
if (empty($plugin)) {
$plugin['title'] = t('Broken');
}
if (empty($id)) {
$id = $this->generateDisplayId($plugin_id);
// Generate a unique human-readable name by inspecting the counter at the
// end of the previous display ID, e.g., 'page_1'.
if ($id !== 'default') {
preg_match("/[0-9]+/", $id, $count);
$count = $count[0];
}
else {
$count = '';
}
if (empty($title)) {
// If there is no title provided, use the plugin title, and if there are
// multiple displays, append the count.
$title = $plugin['title'];
if ($count > 1) {
$title .= ' ' . $count;
}
}
}
$display_options = array(
'display_plugin' => $plugin_id,
'id' => $id,
// Cast the display title to a string since it is an object.
// @see \Drupal\Core\StringTranslation\TranslatableMarkup
'display_title' => (string) $title,
'position' => $id === 'default' ? 0 : count($this->display),
'display_options' => array(),
);
// Add the display options to the view.
$this->display[$id] = $display_options;
return $id;
}
/**
* Generates a display ID of a certain plugin type.
*
* @param string $plugin_id
* Which plugin should be used for the new display ID.
*
* @return string
*/
protected function generateDisplayId($plugin_id) {
// 'default' is singular and is unique, so just go with 'default'
// for it. For all others, start counting.
if ($plugin_id == 'default') {
return 'default';
}
// Initial ID.
$id = $plugin_id . '_1';
$count = 1;
// Loop through IDs based upon our style plugin name until
// we find one that is unused.
while (!empty($this->display[$id])) {
$id = $plugin_id . '_' . ++$count;
}
return $id;
}
/**
* {@inheritdoc}
*/
public function &getDisplay($display_id) {
return $this->display[$display_id];
}
/**
* {@inheritdoc}
*/
public function duplicateDisplayAsType($old_display_id, $new_display_type) {
$executable = $this->getExecutable();
$display = $executable->newDisplay($new_display_type);
$new_display_id = $display->display['id'];
$displays = $this->get('display');
// Let the display title be generated by the addDisplay method and set the
// right display plugin, but keep the rest from the original display.
$display_duplicate = $displays[$old_display_id];
unset($display_duplicate['display_title']);
unset($display_duplicate['display_plugin']);
$displays[$new_display_id] = NestedArray::mergeDeep($displays[$new_display_id], $display_duplicate);
$displays[$new_display_id]['id'] = $new_display_id;
// First set the displays.
$this->set('display', $displays);
// Ensure that we just copy display options, which are provided by the new
// display plugin.
$executable->setDisplay($new_display_id);
$executable->display_handler->filterByDefinedOptions($displays[$new_display_id]['display_options']);
// Update the display settings.
$this->set('display', $displays);
return $new_display_id;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
parent::calculateDependencies();
// Ensure that the view is dependant on the module that implements the view.
$this->addDependency('module', $this->module);
$executable = $this->getExecutable();
$executable->initDisplay();
$executable->initStyle();
foreach ($executable->displayHandlers as $display) {
// Calculate the dependencies each display has.
$this->calculatePluginDependencies($display);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
// Sort the displays.
$display = $this->get('display');
ksort($display);
$this->set('display', array('default' => $display['default']) + $display);
// @todo Check whether isSyncing is needed.
if (!$this->isSyncing()) {
$this->addCacheMetadata();
}
}
/**
* Fills in the cache metadata of this view.
*
* Cache metadata is set per view and per display, and ends up being stored in
* the view's configuration. This allows Views to determine very efficiently:
* - the max-age
* - the cache contexts
* - the cache tags
*
* In other words: this allows us to do the (expensive) work of initializing
* Views plugins and handlers to determine their effect on the cacheability of
* a view at save time rather than at runtime.
*/
protected function addCacheMetadata() {
$executable = $this->getExecutable();
$current_display = $executable->current_display;
$displays = $this->get('display');
foreach (array_keys($displays) as $display_id) {
$display =& $this->getDisplay($display_id);
$executable->setDisplay($display_id);
$cache_metadata = $executable->getDisplay()->calculateCacheMetadata();
$display['cache_metadata']['max-age'] = $cache_metadata->getCacheMaxAge();
$display['cache_metadata']['contexts'] = $cache_metadata->getCacheContexts();
$display['cache_metadata']['tags'] = $cache_metadata->getCacheTags();
// Always include at least the 'languages:' context as there will most
// probably be translatable strings in the view output.
$display['cache_metadata']['contexts'] = Cache::mergeContexts($display['cache_metadata']['contexts'], ['languages:' . LanguageInterface::TYPE_INTERFACE]);
}
// Restore the previous active display.
$executable->setDisplay($current_display);
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
// @todo Remove if views implements a view_builder controller.
views_invalidate_cache();
$this->invalidateCaches();
// Rebuild the router if this is a new view, or it's status changed.
if (!isset($this->original) || ($this->status() != $this->original->status())) {
\Drupal::service('router.builder')->setRebuildNeeded();
}
}
/**
* {@inheritdoc}
*/
public static function postLoad(EntityStorageInterface $storage, array &$entities) {
parent::postLoad($storage, $entities);
foreach ($entities as $entity) {
$entity->mergeDefaultDisplaysOptions();
}
}
/**
* {@inheritdoc}
*/
public static function preCreate(EntityStorageInterface $storage, array &$values) {
parent::preCreate($storage, $values);
// If there is no information about displays available add at least the
// default display.
$values += array(
'display' => array(
'default' => array(
'display_plugin' => 'default',
'id' => 'default',
'display_title' => 'Master',
'position' => 0,
'display_options' => array(),
),
)
);
}
/**
* {@inheritdoc}
*/
public function postCreate(EntityStorageInterface $storage) {
parent::postCreate($storage);
$this->mergeDefaultDisplaysOptions();
}
/**
* {@inheritdoc}
*/
public static function preDelete(EntityStorageInterface $storage, array $entities) {
parent::preDelete($storage, $entities);
// Call the remove() hook on the individual displays.
/** @var \Drupal\views\ViewEntityInterface $entity */
foreach ($entities as $entity) {
$executable = Views::executableFactory()->get($entity);
foreach ($entity->get('display') as $display_id => $display) {
$executable->setDisplay($display_id);
$executable->getDisplay()->remove();
}
}
}
/**
* {@inheritdoc}
*/
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
$tempstore = \Drupal::service('user.shared_tempstore')->get('views');
foreach ($entities as $entity) {
$tempstore->delete($entity->id());
}
}
/**
* {@inheritdoc}
*/
public function mergeDefaultDisplaysOptions() {
$displays = array();
foreach ($this->get('display') as $key => $options) {
$options += array(
'display_options' => array(),
'display_plugin' => NULL,
'id' => NULL,
'display_title' => '',
'position' => NULL,
);
// Add the defaults for the display.
$displays[$key] = $options;
}
$this->set('display', $displays);
}
/**
* {@inheritdoc}
*/
public function isInstallable() {
$table_definition = \Drupal::service('views.views_data')->get($this->base_table);
// Check whether the base table definition exists and contains a base table
// definition. For example, taxonomy_views_data_alter() defines
// node_field_data even if it doesn't exist as a base table.
return $table_definition && isset($table_definition['table']['base']);
}
/**
* {@inheritdoc}
*/
public function __sleep() {
$keys = parent::__sleep();
unset($keys[array_search('executable', $keys)]);
return $keys;
}
/**
* Invalidates cache tags.
*/
public function invalidateCaches() {
// Invalidate cache tags for cached rows.
$tags = $this->getCacheTags();
\Drupal::service('cache_tags.invalidator')->invalidateTags($tags);
}
}

View file

@ -0,0 +1,635 @@
<?php
namespace Drupal\views;
use Drupal\Core\Entity\ContentEntityType;
use Drupal\Core\Entity\EntityHandlerInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\Core\Entity\Sql\TableMappingInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides generic views integration for entities.
*/
class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterface {
use StringTranslationTrait;
/**
* Entity type for this views data handler instance.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
protected $entityType;
/**
* The storage used for this entity type.
*
* @var \Drupal\Core\Entity\Sql\SqlEntityStorageInterface
*/
protected $storage;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The translation manager.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
/**
* The field storage definitions for all base fields of the entity type.
*
* @var \Drupal\Core\Field\FieldStorageDefinitionInterface[]
*/
protected $fieldStorageDefinitions;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs an EntityViewsData object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type to provide views integration for.
* @param \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage_controller
* The storage handler used for this entity type.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
* The translation manager.
*/
function __construct(EntityTypeInterface $entity_type, SqlEntityStorageInterface $storage_controller, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, TranslationInterface $translation_manager) {
$this->entityType = $entity_type;
$this->entityManager = $entity_manager;
$this->storage = $storage_controller;
$this->moduleHandler = $module_handler;
$this->setStringTranslation($translation_manager);
}
/**
* {@inheritdoc}
*/
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('entity.manager'),
$container->get('module_handler'),
$container->get('string_translation'),
$container->get('typed_data_manager')
);
}
/**
* Gets the field storage definitions.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface[]
*/
protected function getFieldStorageDefinitions() {
if (!isset($this->fieldStorageDefinitions)) {
$this->fieldStorageDefinitions = $this->entityManager->getFieldStorageDefinitions($this->entityType->id());
}
return $this->fieldStorageDefinitions;
}
/**
* {@inheritdoc}
*/
public function getViewsData() {
$data = [];
$base_table = $this->entityType->getBaseTable() ?: $this->entityType->id();
$views_revision_base_table = NULL;
$revisionable = $this->entityType->isRevisionable();
$base_field = $this->entityType->getKey('id');
$revision_table = '';
if ($revisionable) {
$revision_table = $this->entityType->getRevisionTable() ?: $this->entityType->id() . '_revision';
}
$translatable = $this->entityType->isTranslatable();
$data_table = '';
if ($translatable) {
$data_table = $this->entityType->getDataTable() ?: $this->entityType->id() . '_field_data';
}
// Some entity types do not have a revision data table defined, but still
// have a revision table name set in
// \Drupal\Core\Entity\Sql\SqlContentEntityStorage::initTableLayout() so we
// apply the same kind of logic.
$revision_data_table = '';
if ($revisionable && $translatable) {
$revision_data_table = $this->entityType->getRevisionDataTable() ?: $this->entityType->id() . '_field_revision';
}
$revision_field = $this->entityType->getKey('revision');
// Setup base information of the views data.
$data[$base_table]['table']['group'] = $this->entityType->getLabel();
$data[$base_table]['table']['provider'] = $this->entityType->getProvider();
$views_base_table = $base_table;
if ($data_table) {
$views_base_table = $data_table;
}
$data[$views_base_table]['table']['base'] = [
'field' => $base_field,
'title' => $this->entityType->getLabel(),
'cache_contexts' => $this->entityType->getListCacheContexts(),
];
$data[$base_table]['table']['entity revision'] = FALSE;
if ($label_key = $this->entityType->getKey('label')) {
if ($data_table) {
$data[$views_base_table]['table']['base']['defaults'] = array(
'field' => $label_key,
'table' => $data_table,
);
}
else {
$data[$views_base_table]['table']['base']['defaults'] = array(
'field' => $label_key,
);
}
}
// Entity types must implement a list_builder in order to use Views'
// entity operations field.
if ($this->entityType->hasListBuilderClass()) {
$data[$base_table]['operations'] = array(
'field' => array(
'title' => $this->t('Operations links'),
'help' => $this->t('Provides links to perform entity operations.'),
'id' => 'entity_operations',
),
);
}
if ($this->entityType->hasViewBuilderClass()) {
$data[$base_table]['rendered_entity'] = [
'field' => [
'title' => $this->t('Rendered entity'),
'help' => $this->t('Renders an entity in a view mode.'),
'id' => 'rendered_entity',
],
];
}
// Setup relations to the revisions/property data.
if ($data_table) {
$data[$base_table]['table']['join'][$data_table] = [
'left_field' => $base_field,
'field' => $base_field,
'type' => 'INNER'
];
$data[$data_table]['table']['group'] = $this->entityType->getLabel();
$data[$data_table]['table']['provider'] = $this->entityType->getProvider();
$data[$data_table]['table']['entity revision'] = FALSE;
}
if ($revision_table) {
$data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
$data[$revision_table]['table']['provider'] = $this->entityType->getProvider();
$views_revision_base_table = $revision_table;
if ($revision_data_table) {
$views_revision_base_table = $revision_data_table;
}
$data[$views_revision_base_table]['table']['entity revision'] = TRUE;
$data[$views_revision_base_table]['table']['base'] = array(
'field' => $revision_field,
'title' => $this->t('@entity_type revisions', array('@entity_type' => $this->entityType->getLabel())),
);
// Join the revision table to the base table.
$data[$views_revision_base_table]['table']['join'][$views_base_table] = array(
'left_field' => $revision_field,
'field' => $revision_field,
'type' => 'INNER',
);
if ($revision_data_table) {
$data[$revision_data_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
$data[$revision_data_table]['table']['entity revision'] = TRUE;
$data[$revision_table]['table']['join'][$revision_data_table] = array(
'left_field' => $revision_field,
'field' => $revision_field,
'type' => 'INNER',
);
}
}
$this->addEntityLinks($data[$base_table]);
// Load all typed data definitions of all fields. This should cover each of
// the entity base, revision, data tables.
$field_definitions = $this->entityManager->getBaseFieldDefinitions($this->entityType->id());
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
if ($table_mapping = $this->storage->getTableMapping($field_definitions)) {
// Fetch all fields that can appear in both the base table and the data
// table.
$entity_keys = $this->entityType->getKeys();
$duplicate_fields = array_intersect_key($entity_keys, array_flip(['id', 'revision', 'bundle']));
// Iterate over each table we have so far and collect field data for each.
// Based on whether the field is in the field_definitions provided by the
// entity manager.
// @todo We should better just rely on information coming from the entity
// storage.
// @todo https://www.drupal.org/node/2337511
foreach ($table_mapping->getTableNames() as $table) {
foreach ($table_mapping->getFieldNames($table) as $field_name) {
// To avoid confusing duplication in the user interface, for fields
// that are on both base and data tables, only add them on the data
// table (same for revision vs. revision data).
if ($data_table && ($table === $base_table || $table === $revision_table) && in_array($field_name, $duplicate_fields)) {
continue;
}
$this->mapFieldDefinition($table, $field_name, $field_definitions[$field_name], $table_mapping, $data[$table]);
}
}
foreach ($field_definitions as $field_definition) {
if ($table_mapping->requiresDedicatedTableStorage($field_definition->getFieldStorageDefinition())) {
$table = $table_mapping->getDedicatedDataTableName($field_definition->getFieldStorageDefinition());
$data[$table]['table']['group'] = $this->entityType->getLabel();
$data[$table]['table']['provider'] = $this->entityType->getProvider();
$data[$table]['table']['join'][$views_base_table] = [
'left_field' => $base_field,
'field' => 'entity_id',
'extra' => [
['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
],
];
if ($revisionable) {
$revision_table = $table_mapping->getDedicatedRevisionTableName($field_definition->getFieldStorageDefinition());
$data[$revision_table]['table']['group'] = $this->t('@entity_type revision', ['@entity_type' => $this->entityType->getLabel()]);
$data[$revision_table]['table']['provider'] = $this->entityType->getProvider();
$data[$revision_table]['table']['join'][$views_revision_base_table] = [
'left_field' => $revision_field,
'field' => 'entity_id',
'extra' => [
['field' => 'deleted', 'value' => 0, 'numeric' => TRUE],
],
];
}
}
}
}
// 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){
$table_data['table']['entity type'] = $entity_type_id;
});
return $data;
}
/**
* Sets the entity links in case corresponding link templates exist.
*
* @param array $data
* The views data of the base table.
*/
protected function addEntityLinks(array &$data) {
$entity_type_id = $this->entityType->id();
$t_arguments = ['@entity_type_label' => $this->entityType->getLabel()];
if ($this->entityType->hasLinkTemplate('canonical')) {
$data['view_' . $entity_type_id] = [
'field' => [
'title' => $this->t('Link to @entity_type_label', $t_arguments),
'help' => $this->t('Provide a view link to the @entity_type_label.', $t_arguments),
'id' => 'entity_link',
],
];
}
if ($this->entityType->hasLinkTemplate('edit-form')) {
$data['edit_' . $entity_type_id] = [
'field' => [
'title' => $this->t('Link to edit @entity_type_label', $t_arguments),
'help' => $this->t('Provide an edit link to the @entity_type_label.', $t_arguments),
'id' => 'entity_link_edit',
],
];
}
if ($this->entityType->hasLinkTemplate('delete-form')) {
$data['delete_' . $entity_type_id] = [
'field' => [
'title' => $this->t('Link to delete @entity_type_label', $t_arguments),
'help' => $this->t('Provide a delete link to the @entity_type_label.', $t_arguments),
'id' => 'entity_link_delete',
],
];
}
}
/**
* Puts the views data for a single field onto the views data.
*
* @param string $table
* The table of the field to handle.
* @param string $field_name
* The name of the field to handle.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition defined in Entity::baseFieldDefinitions()
* @param \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping
* The table mapping information
* @param array $table_data
* A reference to a specific entity table (for example data_table) inside
* the views data.
*/
protected function mapFieldDefinition($table, $field_name, FieldDefinitionInterface $field_definition, TableMappingInterface $table_mapping, &$table_data) {
// Create a dummy instance to retrieve property definitions.
$field_column_mapping = $table_mapping->getColumnNames($field_name);
$field_schema = $this->getFieldStorageDefinitions()[$field_name]->getSchema();
$field_definition_type = $field_definition->getType();
// Add all properties to views table data. We need an entry for each
// column of each field, with the first one given special treatment.
// @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;
$first = FALSE;
}
}
/**
* Provides the views data for a given data type and schema field.
*
* @param string $table
* The table of the field to handle.
* @param string $field_name
* The machine name of the field being processed.
* @param string $field_type
* The type of field being handled.
* @param string $column_name
* For fields containing multiple columns, the column name being processed.
* @param string $column_type
* Within the field, the column type being handled.
* @param bool $first
* TRUE if this is the first column within the field.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
*
* @return array
* The modified views data field definition.
*/
protected function mapSingleFieldViewsData($table, $field_name, $field_type, $column_name, $column_type, $first, FieldDefinitionInterface $field_definition) {
$views_field = array();
// Provide a nicer, less verbose label for the first column within a field.
// @todo Introduce concept of the "main" column for a field, rather than
// assuming the first one is the main column.
if ($first) {
$views_field['title'] = $field_definition->getLabel();
}
else {
$views_field['title'] = $field_definition->getLabel() . " ($column_name)";
}
if ($description = $field_definition->getDescription()) {
$views_field['help'] = $description;
}
// Set up the field, sort, argument, and filters, based on
// the column and/or field data type.
// @todo Allow field types to customize this.
// @see https://www.drupal.org/node/2337515
switch ($field_type) {
// Special case a few field types.
case 'timestamp':
case 'created':
case 'changed':
$views_field['field']['id'] = 'field';
$views_field['argument']['id'] = 'date';
$views_field['filter']['id'] = 'date';
$views_field['sort']['id'] = 'date';
break;
case 'language':
$views_field['field']['id'] = 'field';
$views_field['argument']['id'] = 'language';
$views_field['filter']['id'] = 'language';
$views_field['sort']['id'] = 'standard';
break;
case 'boolean':
$views_field['field']['id'] = 'field';
$views_field['argument']['id'] = 'numeric';
$views_field['filter']['id'] = 'boolean';
$views_field['sort']['id'] = 'standard';
break;
case 'uri':
// Let's render URIs as URIs by default, not links.
$views_field['field']['id'] = 'field';
$views_field['field']['default_formatter'] = 'string';
$views_field['argument']['id'] = 'string';
$views_field['filter']['id'] = 'string';
$views_field['sort']['id'] = 'standard';
break;
case 'text':
case 'text_with_summary':
// Treat these three long text fields the same.
$field_type = 'text_long';
// Intentional fall-through here to the default processing!
default:
// For most fields, the field type is generic enough to just use
// the column type to determine the filters etc.
switch ($column_type) {
case 'int':
case 'integer':
case 'smallint':
case 'tinyint':
case 'mediumint':
case 'float':
case 'double':
case 'decimal':
$views_field['field']['id'] = 'field';
$views_field['argument']['id'] = 'numeric';
$views_field['filter']['id'] = 'numeric';
$views_field['sort']['id'] = 'standard';
break;
case 'char':
case 'string':
case 'varchar':
case 'varchar_ascii':
case 'tinytext':
case 'text':
case 'mediumtext':
case 'longtext':
$views_field['field']['id'] = 'field';
$views_field['argument']['id'] = 'string';
$views_field['filter']['id'] = 'string';
$views_field['sort']['id'] = 'standard';
break;
default:
$views_field['field']['id'] = 'field';
$views_field['argument']['id'] = 'standard';
$views_field['filter']['id'] = 'standard';
$views_field['sort']['id'] = 'standard';
}
}
// Do post-processing for a few field types.
$process_method = 'processViewsDataFor' . Container::camelize($field_type);
if (method_exists($this, $process_method)) {
$this->{$process_method}($table, $field_definition, $views_field, $column_name);
}
return $views_field;
}
/**
* Processes the views data for a language field.
*
* @param string $table
* The table the language field is added to.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param array $views_field
* The views field data.
* @param string $field_column_name
* The field column being processed.
*/
protected function processViewsDataForLanguage($table, FieldDefinitionInterface $field_definition, array &$views_field, $field_column_name) {
// Apply special titles for the langcode field.
if ($field_definition->getName() == $this->entityType->getKey('langcode')) {
if ($table == $this->entityType->getDataTable() || $table == $this->entityType->getRevisionDataTable()) {
$views_field['title'] = $this->t('Translation language');
}
if ($table == $this->entityType->getBaseTable() || $table == $this->entityType->getRevisionTable()) {
$views_field['title'] = $this->t('Original language');
}
}
}
/**
* Processes the views data for an entity reference field.
*
* @param string $table
* The table the language field is added to.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param array $views_field
* The views field data.
* @param string $field_column_name
* The field column being processed.
*/
protected function processViewsDataForEntityReference($table, FieldDefinitionInterface $field_definition, array &$views_field, $field_column_name) {
// @todo Should the actual field handler respect that this just renders a
// number?
// @todo Create an optional entity field handler, that can render the
// entity.
// @see https://www.drupal.org/node/2322949
if ($entity_type_id = $field_definition->getItemDefinition()->getSetting('target_type')) {
$entity_type = $this->entityManager->getDefinition($entity_type_id);
if ($entity_type instanceof ContentEntityType) {
$views_field['relationship'] = [
'base' => $this->getViewsTableForEntityType($entity_type),
'base field' => $entity_type->getKey('id'),
'label' => $entity_type->getLabel(),
'title' => $entity_type->getLabel(),
'id' => 'standard',
];
$views_field['field']['id'] = 'field';
$views_field['argument']['id'] = 'numeric';
$views_field['filter']['id'] = 'numeric';
$views_field['sort']['id'] = 'standard';
}
else {
$views_field['field']['id'] = 'field';
$views_field['argument']['id'] = 'string';
$views_field['filter']['id'] = 'string';
$views_field['sort']['id'] = 'standard';
}
}
if ($field_definition->getName() == $this->entityType->getKey('bundle')) {
$views_field['filter']['id'] = 'bundle';
}
}
/**
* Processes the views data for a text field with formatting.
*
* @param string $table
* The table the field is added to.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param array $views_field
* The views field data.
* @param string $field_column_name
* The field column being processed.
*/
protected function processViewsDataForTextLong($table, FieldDefinitionInterface $field_definition, array &$views_field, $field_column_name) {
// Connect the text field to its formatter.
if ($field_column_name == 'value') {
$views_field['field']['format'] = $field_definition->getName() . '__format';
$views_field['field']['id'] = 'field';
}
}
/**
* Processes the views data for a UUID field.
*
* @param string $table
* The table the field is added to.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The field definition.
* @param array $views_field
* The views field data.
* @param string $field_column_name
* The field column being processed.
*/
protected function processViewsDataForUuid($table, FieldDefinitionInterface $field_definition, array &$views_field, $field_column_name) {
// It does not make sense for UUID fields to be click sortable.
$views_field['field']['click sortable'] = FALSE;
}
/**
* {@inheritdoc}
*/
public function getViewsTableForEntityType(EntityTypeInterface $entity_type) {
return $entity_type->getDataTable() ?: $entity_type->getBaseTable();
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Drupal\views;
use Drupal\Core\Entity\EntityTypeInterface;
/**
* Provides an interface to integrate an entity type with views.
*/
interface EntityViewsDataInterface {
/**
* Returns views data for the entity type.
*
* @return array
* Views data in the format of hook_views_data().
*/
public function getViewsData();
/**
* Gets the table of an entity type to be used as base table in views.
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
*
* @return string
* The name of the base table in views.
*/
public function getViewsTableForEntityType(EntityTypeInterface $entity_type);
}

View file

@ -0,0 +1,175 @@
<?php
namespace Drupal\views\EventSubscriber;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Drupal\views\Plugin\views\display\DisplayRouterInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
use Symfony\Component\Routing\RouteCollection;
/**
* Builds up the routes of all views.
*
* The general idea is to execute first all alter hooks to determine which
* routes are overridden by views. This information is used to determine which
* views have to be added by views in the dynamic event.
*
*
* @see \Drupal\views\Plugin\views\display\PathPluginBase
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* Stores a list of view,display IDs which haven't be used in the alter event.
*
* @var array
*/
protected $viewsDisplayPairs;
/**
* The view storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $viewStorage;
/**
* The state key value store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Stores an array of route names keyed by view_id.display_id.
*
* @var array
*/
protected $viewRouteNames = array();
/**
* Constructs a \Drupal\views\EventSubscriber\RouteSubscriber instance.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
*/
public function __construct(EntityManagerInterface $entity_manager, StateInterface $state) {
$this->viewStorage = $entity_manager->getStorage('view');
$this->state = $state;
}
/**
* Resets the internal state of the route subscriber.
*/
public function reset() {
$this->viewsDisplayPairs = NULL;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = parent::getSubscribedEvents();
$events[RoutingEvents::FINISHED] = array('routeRebuildFinished');
// Ensure to run after the entity resolver subscriber
// @see \Drupal\Core\EventSubscriber\EntityRouteAlterSubscriber
$events[RoutingEvents::ALTER] = ['onAlterRoutes', -175];
return $events;
}
/**
* Gets all the views and display IDs using a route.
*/
protected function getViewsDisplayIDsWithRoute() {
if (!isset($this->viewsDisplayPairs)) {
$this->viewsDisplayPairs = array();
// @todo Convert this method to some service.
$views = $this->getApplicableViews();
foreach ($views as $data) {
list($view_id, $display_id) = $data;
$this->viewsDisplayPairs[] = $view_id . '.' . $display_id;
}
$this->viewsDisplayPairs = array_combine($this->viewsDisplayPairs, $this->viewsDisplayPairs);
}
return $this->viewsDisplayPairs;
}
/**
* Returns a set of route objects.
*
* @return \Symfony\Component\Routing\RouteCollection
* A route collection.
*/
public function routes() {
$collection = new RouteCollection();
foreach ($this->getViewsDisplayIDsWithRoute() as $pair) {
list($view_id, $display_id) = explode('.', $pair);
$view = $this->viewStorage->load($view_id);
// @todo This should have an executable factory injected.
if (($view = $view->getExecutable()) && $view instanceof ViewExecutable) {
if ($view->setDisplay($display_id) && $display = $view->displayHandlers->get($display_id)) {
if ($display instanceof DisplayRouterInterface) {
$this->viewRouteNames += (array) $display->collectRoutes($collection);
}
}
$view->destroy();
}
}
$this->state->set('views.view_route_names', $this->viewRouteNames);
return $collection;
}
/**
* {@inheritdoc}
*/
protected function alterRoutes(RouteCollection $collection) {
foreach ($this->getViewsDisplayIDsWithRoute() as $pair) {
list($view_id, $display_id) = explode('.', $pair);
$view = $this->viewStorage->load($view_id);
// @todo This should have an executable factory injected.
if (($view = $view->getExecutable()) && $view instanceof ViewExecutable) {
if ($view->setDisplay($display_id) && $display = $view->displayHandlers->get($display_id)) {
if ($display instanceof DisplayRouterInterface) {
// If the display returns TRUE a route item was found, so it does not
// have to be added.
$view_route_names = $display->alterRoutes($collection);
$this->viewRouteNames = $view_route_names + $this->viewRouteNames;
foreach ($view_route_names as $id_display => $route_name) {
$view_route_name = $this->viewsDisplayPairs[$id_display];
unset($this->viewsDisplayPairs[$id_display]);
$collection->remove("views.$view_route_name");
}
}
}
$view->destroy();
}
}
}
/**
* {@inheritdoc}
*/
public function routeRebuildFinished() {
$this->reset();
$this->state->set('views.view_route_names', $this->viewRouteNames);
}
/**
* Returns all views/display combinations with routes.
*
* @see \Drupal\views\Views::getApplicableViews()
*/
protected function getApplicableViews() {
return Views::getApplicableViews('uses_route');
}
}

View file

@ -0,0 +1,375 @@
<?php
namespace Drupal\views\EventSubscriber;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeListenerInterface;
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
use Drupal\views\Views;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Reacts to changes on entity types to update all views entities.
*/
class ViewsEntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscriberInterface {
use EntityTypeEventSubscriberTrait;
/**
* Indicates that a base table got renamed.
*/
const BASE_TABLE_RENAME = 0;
/**
* Indicates that a data table got renamed.
*/
const DATA_TABLE_RENAME = 1;
/**
* Indicates that a data table got added.
*/
const DATA_TABLE_ADDITION = 2;
/**
* Indicates that a data table got removed.
*/
const DATA_TABLE_REMOVAL = 3;
/**
* Indicates that a revision table got renamed.
*/
const REVISION_TABLE_RENAME = 4;
/**
* Indicates that a revision table got added.
*/
const REVISION_TABLE_ADDITION = 5;
/**
* Indicates that a revision table got removed.
*/
const REVISION_TABLE_REMOVAL = 6;
/**
* Indicates that a revision data table got renamed.
*/
const REVISION_DATA_TABLE_RENAME = 7;
/**
* Indicates that a revision data table got added.
*/
const REVISION_DATA_TABLE_ADDITION = 8;
/**
* Indicates that a revision data table got removed.
*/
const REVISION_DATA_TABLE_REMOVAL = 9;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a ViewsEntitySchemaSubscriber.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entity_manager) {
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
return static::getEntityTypeEvents();
}
/**
* {@inheritdoc}
*/
public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
$changes = [];
// We implement a specific logic for table updates, which is bound to the
// default sql content entity storage.
if (!$this->entityManager->getStorage($entity_type->id()) instanceof SqlContentEntityStorage) {
return;
}
if ($entity_type->getBaseTable() != $original->getBaseTable()) {
$changes[] = static::BASE_TABLE_RENAME;
}
$revision_add = $entity_type->isRevisionable() && !$original->isRevisionable();
$revision_remove = !$entity_type->isRevisionable() && $original->isRevisionable();
$translation_add = $entity_type->isTranslatable() && !$original->isTranslatable();
$translation_remove = !$entity_type->isTranslatable() && $original->isTranslatable();
if ($revision_add) {
$changes[] = static::REVISION_TABLE_ADDITION;
}
elseif ($revision_remove) {
$changes[] = static::REVISION_TABLE_REMOVAL;
}
elseif ($entity_type->isRevisionable() && $entity_type->getRevisionTable() != $original->getRevisionTable()) {
$changes[] = static::REVISION_TABLE_RENAME;
}
if ($translation_add) {
$changes[] = static::DATA_TABLE_ADDITION;
}
elseif ($translation_remove) {
$changes[] = static::DATA_TABLE_REMOVAL;
}
elseif ($entity_type->isTranslatable() && $entity_type->getDataTable() != $original->getDataTable()) {
$changes[] = static::DATA_TABLE_RENAME;
}
if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
if ($revision_add || $translation_add) {
$changes[] = static::REVISION_DATA_TABLE_ADDITION;
}
elseif ($entity_type->getRevisionDataTable() != $original->getRevisionDataTable()) {
$changes[] = static::REVISION_DATA_TABLE_RENAME;
}
}
elseif ($original->isRevisionable() && $original->isTranslatable() && ($revision_remove || $translation_remove)) {
$changes[] = static::REVISION_DATA_TABLE_REMOVAL;
}
/** @var \Drupal\views\Entity\View[] $all_views */
$all_views = $this->entityManager->getStorage('view')->loadMultiple(NULL);
foreach ($changes as $change) {
switch ($change) {
case static::BASE_TABLE_RENAME:
$this->baseTableRename($all_views, $entity_type->id(), $original->getBaseTable(), $entity_type->getBaseTable());
break;
case static::DATA_TABLE_RENAME:
$this->dataTableRename($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getDataTable());
break;
case static::DATA_TABLE_ADDITION:
$this->dataTableAddition($all_views, $entity_type, $entity_type->getDataTable(), $entity_type->getBaseTable());
break;
case static::DATA_TABLE_REMOVAL:
$this->dataTableRemoval($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getBaseTable());
break;
case static::REVISION_TABLE_RENAME:
$this->baseTableRename($all_views, $entity_type->id(), $original->getRevisionTable(), $entity_type->getRevisionTable());
break;
case static::REVISION_TABLE_ADDITION:
// If we add revision support we don't have to do anything.
break;
case static::REVISION_TABLE_REMOVAL:
$this->revisionRemoval($all_views, $original);
break;
case static::REVISION_DATA_TABLE_RENAME:
$this->dataTableRename($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionDataTable());
break;
case static::REVISION_DATA_TABLE_ADDITION:
$this->dataTableAddition($all_views, $entity_type, $entity_type->getRevisionDataTable(), $entity_type->getRevisionTable());
break;
case static::REVISION_DATA_TABLE_REMOVAL:
$this->dataTableRemoval($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionTable());
break;
}
}
foreach ($all_views as $view) {
// All changes done to the views here can be trusted and this might be
// called during updates, when it is not safe to rely on configuration
// containing valid schema. Trust the data and disable schema validation
// and casting.
$view->trustData()->save();
}
}
/**
* {@inheritdoc}
*/
public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
$tables = [
$entity_type->getBaseTable(),
$entity_type->getDataTable(),
$entity_type->getRevisionTable(),
$entity_type->getRevisionDataTable(),
];
$all_views = $this->entityManager->getStorage('view')->loadMultiple(NULL);
/** @var \Drupal\views\Entity\View $view */
foreach ($all_views as $id => $view) {
// First check just the base table.
if (in_array($view->get('base_table'), $tables)) {
$view->disable();
$view->save();
}
}
}
/**
* Applies a callable onto all handlers of all passed in views.
*
* @param \Drupal\views\Entity\View[] $all_views
* All views entities.
* @param callable $process
* A callable which retrieves a handler config array.
*/
protected function processHandlers(array $all_views, callable $process) {
foreach ($all_views as $view) {
foreach (array_keys($view->get('display')) as $display_id) {
$display = &$view->getDisplay($display_id);
foreach (Views::getHandlerTypes() as $handler_type) {
$handler_type = $handler_type['plural'];
if (!isset($display['display_options'][$handler_type])) {
continue;
}
foreach ($display['display_options'][$handler_type] as $id => &$handler_config) {
$process($handler_config);
if ($handler_config === NULL) {
unset($display['display_options'][$handler_type][$id]);
}
}
}
}
}
}
/**
* Updates views if a base table is renamed.
*
* @param \Drupal\views\Entity\View[] $all_views
* All views.
* @param string $entity_type_id
* The entity type ID.
* @param string $old_base_table
* The old base table name.
* @param string $new_base_table
* The new base table name.
*/
protected function baseTableRename($all_views, $entity_type_id, $old_base_table, $new_base_table) {
foreach ($all_views as $view) {
if ($view->get('base_table') == $old_base_table) {
$view->set('base_table', $new_base_table);
}
}
$this->processHandlers($all_views, function (array &$handler_config) use ($entity_type_id, $old_base_table, $new_base_table) {
if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id && $handler_config['table'] == $old_base_table) {
$handler_config['table'] = $new_base_table;
}
});
}
/**
* Updates views if a data table is renamed.
*
* @param \Drupal\views\Entity\View[] $all_views
* All views.
* @param string $entity_type_id
* The entity type ID.
* @param string $old_data_table
* The old data table name.
* @param string $new_data_table
* The new data table name.
*/
protected function dataTableRename($all_views, $entity_type_id, $old_data_table, $new_data_table) {
foreach ($all_views as $view) {
if ($view->get('base_table') == $old_data_table) {
$view->set('base_table', $new_data_table);
}
}
$this->processHandlers($all_views, function (array &$handler_config) use ($entity_type_id, $old_data_table, $new_data_table) {
if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id && $handler_config['table'] == $old_data_table) {
$handler_config['table'] = $new_data_table;
}
});
}
/**
* Updates views if a data table is added.
*
* @param \Drupal\views\Entity\View[] $all_views
* All views.
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type.
* @param string $new_data_table
* The new data table.
* @param string $base_table
* The base table.
*/
protected function dataTableAddition($all_views, EntityTypeInterface $entity_type, $new_data_table, $base_table) {
/** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
$entity_type_id = $entity_type->id();
$storage = $this->entityManager->getStorage($entity_type_id);
$storage->setEntityType($entity_type);
$table_mapping = $storage->getTableMapping();
$data_table_fields = $table_mapping->getFieldNames($new_data_table);
$base_table_fields = $table_mapping->getFieldNames($base_table);
$data_table = $new_data_table;
$this->processHandlers($all_views, function (array &$handler_config) use ($entity_type_id, $base_table, $data_table, $base_table_fields, $data_table_fields) {
if (isset($handler_config['entity_type']) && isset($handler_config['entity_field']) && $handler_config['entity_type'] == $entity_type_id) {
// Move all fields which just exists on the data table.
if ($handler_config['table'] == $base_table && in_array($handler_config['entity_field'], $data_table_fields) && !in_array($handler_config['entity_field'], $base_table_fields)) {
$handler_config['table'] = $data_table;
}
}
});
}
/**
* Updates views if a data table is removed.
*
* @param \Drupal\views\Entity\View[] $all_views
* All views.
* @param string $entity_type_id
* The entity type ID.
* @param string $old_data_table
* The name of the previous existing data table.
* @param string $base_table
* The name of the base table.
*/
protected function dataTableRemoval($all_views, $entity_type_id, $old_data_table, $base_table) {
// We move back the data table back to the base table.
$this->processHandlers($all_views, function (array &$handler_config) use ($entity_type_id, $old_data_table, $base_table) {
if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id) {
if ($handler_config['table'] == $old_data_table) {
$handler_config['table'] = $base_table;
}
}
});
}
/**
* Updates views if revision support is removed
*
* @param \Drupal\views\Entity\View[] $all_views
* All views.
* @param \Drupal\Core\Entity\EntityTypeInterface $original
* The origin entity type.
*/
protected function revisionRemoval($all_views, EntityTypeInterface $original) {
$revision_base_table = $original->getRevisionTable();
$revision_data_table = $original->getRevisionDataTable();
foreach ($all_views as $view) {
if (in_array($view->get('base_table'), [$revision_base_table, $revision_data_table])) {
// Let's disable the views as we no longer support revisions.
$view->setStatus(FALSE);
}
// For any kind of field, let's rely on the broken handler functionality.
}
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Drupal\views;
/**
* Caches exposed forms, as they are heavy to generate.
*
* @see \Drupal\views\Form\ViewsExposedForm
*/
class ExposedFormCache {
/**
* Stores the exposed form data.
*
* @var array
*/
protected $cache = array();
/**
* Save the Views exposed form for later use.
*
* @param string $view_id
* The views ID.
* @param string $display_id
* The current view display name.
* @param array $form_output
* The form structure. Only needed when inserting the value.
*/
public function setForm($view_id, $display_id, array $form_output) {
// Save the form output.
$views_exposed[$view_id][$display_id] = $form_output;
}
/**
* Retrieves the views exposed form from cache.
*
* @param string $view_id
* The views ID.
* @param string $display_id
* The current view display name.
*
* @return array|bool
* The form structure, if any, otherwise FALSE.
*/
public function getForm($view_id, $display_id) {
// Return the form output, if any.
if (empty($this->cache[$view_id][$display_id])) {
return FALSE;
}
else {
return $this->cache[$view_id][$display_id];
}
}
/**
* Rests the form cache.
*/
public function reset() {
$this->cache = array();
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Drupal\views;
use Drupal\Core\Field\BaseFieldDefinition;
/**
* A trait containing helper methods for field definitions.
*/
trait FieldAPIHandlerTrait {
/**
* The field definition.
*
* @var \Drupal\Core\Field\FieldDefinitionInterface
*/
protected $fieldDefinition;
/**
* The field storage definition.
*
* @var \Drupal\field\FieldStorageConfigInterface
*/
protected $fieldStorageDefinition;
/**
* Gets the field definition.
*
* A View works on an entity type across bundles, and thus only has access to
* field storage definitions. In order to be able to use widgets and
* formatters, we create a generic field definition out of that storage
* definition.
*
* @see BaseFieldDefinition::createFromFieldStorageDefinition()
*
* @return \Drupal\Core\Field\FieldDefinitionInterface
* The field definition used by this handler.
*/
protected function getFieldDefinition() {
if (!$this->fieldDefinition) {
$field_storage_config = $this->getFieldStorageDefinition();
$this->fieldDefinition = BaseFieldDefinition::createFromFieldStorageDefinition($field_storage_config);
}
return $this->fieldDefinition;
}
/**
* Gets the field storage configuration.
*
* @return \Drupal\field\FieldStorageConfigInterface
* The field storage definition used by this handler
*/
protected function getFieldStorageDefinition() {
if (!$this->fieldStorageDefinition) {
$field_storage_definitions = $this->getEntityManager()->getFieldStorageDefinitions($this->definition['entity_type']);
$this->fieldStorageDefinition = $field_storage_definitions[$this->definition['field_name']];
}
return $this->fieldStorageDefinition;
}
/**
* Returns the entity manager.
*
* @return \Drupal\Core\Entity\EntityManagerInterface
* The entity manager service.
*/
protected function getEntityManager() {
if (!isset($this->entityManager)) {
$this->entityManager = \Drupal::entityManager();
}
return $this->entityManager;
}
}

View file

@ -0,0 +1,194 @@
<?php
namespace Drupal\views\Form;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Checkboxes;
use Drupal\Core\Url;
use Drupal\views\ExposedFormCache;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the views exposed form.
*/
class ViewsExposedForm extends FormBase {
/**
* The exposed form cache.
*
* @var \Drupal\views\ExposedFormCache
*/
protected $exposedFormCache;
/**
* Constructs a new ViewsExposedForm
*
* @param \Drupal\views\ExposedFormCache $exposed_form_cache
* The exposed form cache.
*/
public function __construct(ExposedFormCache $exposed_form_cache) {
$this->exposedFormCache = $exposed_form_cache;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('views.exposed_form_cache'));
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'views_exposed_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
// Don't show the form when batch operations are in progress.
if ($batch = batch_get() && isset($batch['current_set'])) {
return array(
// Set the theme callback to be nothing to avoid errors in template_preprocess_views_exposed_form().
'#theme' => '',
);
}
// Make sure that we validate because this form might be submitted
// multiple times per page.
$form_state->setValidationEnforced();
/** @var \Drupal\views\ViewExecutable $view */
$view = $form_state->get('view');
$display = &$form_state->get('display');
$form_state->setUserInput($view->getExposedInput());
// Let form plugins know this is for exposed widgets.
$form_state->set('exposed', TRUE);
// Check if the form was already created
if ($cache = $this->exposedFormCache->getForm($view->storage->id(), $view->current_display)) {
return $cache;
}
$form['#info'] = array();
// Go through each handler and let it generate its exposed widget.
foreach ($view->display_handler->handlers as $type => $value) {
/** @var \Drupal\views\Plugin\views\ViewsHandlerInterface $handler */
foreach ($view->$type as $id => $handler) {
if ($handler->canExpose() && $handler->isExposed()) {
// Grouped exposed filters have their own forms.
// Instead of render the standard exposed form, a new Select or
// Radio form field is rendered with the available groups.
// When an user choose an option the selected value is split
// into the operator and value that the item represents.
if ($handler->isAGroup()) {
$handler->groupForm($form, $form_state);
$id = $handler->options['group_info']['identifier'];
}
else {
$handler->buildExposedForm($form, $form_state);
}
if ($info = $handler->exposedInfo()) {
$form['#info']["$type-$id"] = $info;
}
}
}
}
$form['actions'] = array(
'#type' => 'actions'
);
$form['actions']['submit'] = array(
// Prevent from showing up in \Drupal::request()->query.
'#name' => '',
'#type' => 'submit',
'#value' => $this->t('Apply'),
'#id' => Html::getUniqueId('edit-submit-' . $view->storage->id()),
);
$form['#action'] = $view->hasUrl() ? $view->getUrl()->toString() : Url::fromRoute('<current>')->toString();
$form['#theme'] = $view->buildThemeFunctions('views_exposed_form');
$form['#id'] = Html::cleanCssIdentifier('views_exposed_form-' . $view->storage->id() . '-' . $display['id']);
/** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form_plugin */
$exposed_form_plugin = $view->display_handler->getPlugin('exposed_form');
$exposed_form_plugin->exposedFormAlter($form, $form_state);
// Save the form.
$this->exposedFormCache->setForm($view->storage->id(), $view->current_display, $form);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->get('view');
foreach (array('field', 'filter') as $type) {
/** @var \Drupal\views\Plugin\views\ViewsHandlerInterface[] $handlers */
$handlers = &$view->$type;
foreach ($handlers as $key => $handler) {
$handlers[$key]->validateExposed($form, $form_state);
}
}
/** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginInterface $exposed_form_plugin */
$exposed_form_plugin = $view->display_handler->getPlugin('exposed_form');
$exposed_form_plugin->exposedFormValidate($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
// Form input keys that will not be included in $view->exposed_raw_data.
$exclude = array('submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', 'reset');
$values = $form_state->getValues();
foreach (array('field', 'filter') as $type) {
/** @var \Drupal\views\Plugin\views\ViewsHandlerInterface[] $handlers */
$handlers = &$form_state->get('view')->$type;
foreach ($handlers as $key => $info) {
if ($handlers[$key]->acceptExposedInput($values)) {
$handlers[$key]->submitExposed($form, $form_state);
}
else {
// The input from the form did not validate, exclude it from the
// stored raw data.
$exclude[] = $key;
}
}
}
$view = $form_state->get('view');
$view->exposed_data = $values;
$view->exposed_raw_input = [];
$exclude = array('submit', 'form_build_id', 'form_id', 'form_token', 'exposed_form_plugin', 'reset');
/** @var \Drupal\views\Plugin\views\exposed_form\ExposedFormPluginBase $exposed_form_plugin */
$exposed_form_plugin = $view->display_handler->getPlugin('exposed_form');
$exposed_form_plugin->exposedFormSubmit($form, $form_state, $exclude);
foreach ($values as $key => $value) {
if (!empty($key) && !in_array($key, $exclude)) {
if (is_array($value)) {
// Handle checkboxes, we only want to include the checked options.
// @todo: revisit the need for this when
// 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];
}
}
else {
$view->exposed_raw_input[$key] = $value;
}
}
}
}
}

View file

@ -0,0 +1,205 @@
<?php
namespace Drupal\views\Form;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Url;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Provides a base class for single- or multistep view forms.
*
* This class only dispatches logic to the form for the current step. The form
* is always assumed to be multistep, even if it has only one step (which by
* default is \Drupal\views\Form\ViewsFormMainForm). That way it is actually
* possible for modules to have a multistep form if they need to.
*/
class ViewsForm implements FormInterface, ContainerInjectionInterface {
use DependencySerializationTrait;
/**
* The class resolver to get the subform form objects.
*
* @var \Drupal\Core\DependencyInjection\ClassResolverInterface
*/
protected $classResolver;
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The url generator to generate the form action.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* The ID of the view.
*
* @var string
*/
protected $viewId;
/**
* The ID of the active view's display.
*
* @var string
*/
protected $viewDisplayId;
/**
* The arguments passed to the active view.
*
* @var string[]
*/
protected $viewArguments;
/**
* Constructs a ViewsForm object.
*
* @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
* The class resolver to get the subform form objects.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The url generator to generate the form action.
* @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
* The request stack.
* @param string $view_id
* The ID of the view.
* @param string $view_display_id
* The ID of the active view's display.
* @param string[] $view_args
* The arguments passed to the active view.
*/
public function __construct(ClassResolverInterface $class_resolver, UrlGeneratorInterface $url_generator, RequestStack $requestStack, $view_id, $view_display_id, array $view_args) {
$this->classResolver = $class_resolver;
$this->urlGenerator = $url_generator;
$this->requestStack = $requestStack;
$this->viewId = $view_id;
$this->viewDisplayId = $view_display_id;
$this->viewArguments = $view_args;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $view_id = NULL, $view_display_id = NULL, array $view_args = NULL) {
return new static(
$container->get('class_resolver'),
$container->get('url_generator'),
$container->get('request_stack'),
$view_id,
$view_display_id,
$view_args
);
}
/**
* Returns a string for the form's base ID.
*
* @return string
* The string identifying the form's base ID.
*/
public function getBaseFormId() {
$parts = [
'views_form',
$this->viewId,
$this->viewDisplayId,
];
return implode('_', $parts);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
$parts = [
$this->getBaseFormId(),
];
if (!empty($this->viewArguments)) {
// Append the passed arguments to ensure form uniqueness.
$parts = array_merge($parts, $this->viewArguments);
}
return implode('_', $parts);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ViewExecutable $view = NULL, $output = []) {
if (!$step = $form_state->get('step')) {
$step = 'views_form_views_form';
$form_state->set('step', $step);
}
$form_state->set(['step_controller', 'views_form_views_form'], 'Drupal\views\Form\ViewsFormMainForm');
// Add the base form ID.
$form_state->addBuildInfo('base_form_id', $this->getBaseFormId());
$form = array();
$query = $this->requestStack->getCurrentRequest()->query->all();
$query = UrlHelper::filterQueryParameters($query, array(), '');
$options = array('query' => $query);
$form['#action'] = $view->hasUrl() ? $view->getUrl()->setOptions($options)->toString() : Url::fromRoute('<current>')->setOptions($options)->toString();
// Tell the preprocessor whether it should hide the header, footer, pager,
// etc.
$form['show_view_elements'] = array(
'#type' => 'value',
'#value' => ($step == 'views_form_views_form') ? TRUE : FALSE,
);
$form_object = $this->getFormObject($form_state);
$form += $form_object->buildForm($form, $form_state, $view, $output);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$form_object = $this->getFormObject($form_state);
$form_object->validateForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_object = $this->getFormObject($form_state);
$form_object->submitForm($form, $form_state);
}
/**
* Returns the object used to build the step form.
*
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form_state of the current form.
*
* @return \Drupal\Core\Form\FormInterface
* The form object to use.
*/
protected function getFormObject(FormStateInterface $form_state) {
// If this is a class, instantiate it.
$form_step_class = $form_state->get(['step_controller', $form_state->get('step')]) ?: 'Drupal\views\Form\ViewsFormMainForm';
return $this->classResolver->getInstanceFromDefinition($form_step_class);
}
}

View file

@ -0,0 +1,146 @@
<?php
namespace Drupal\views\Form;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;
class ViewsFormMainForm implements FormInterface {
/**
* {@inheritdoc}
*/
public function getFormId() {
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, ViewExecutable $view = NULL, $output = []) {
$form['#prefix'] = '<div class="views-form">';
$form['#suffix'] = '</div>';
$form['#pre_render'][] = 'views_pre_render_views_form_views_form';
// Add the output markup to the form array so that it's included when the form
// array is passed to the theme function.
$form['output'] = $output;
// This way any additional form elements will go before the view
// (below the exposed widgets).
$form['output']['#weight'] = 50;
$form['actions'] = array(
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
$substitutions = array();
foreach ($view->field as $field_name => $field) {
$form_element_name = $field_name;
if (method_exists($field, 'form_element_name')) {
$form_element_name = $field->form_element_name();
}
$method_form_element_row_id_exists = FALSE;
if (method_exists($field, 'form_element_row_id')) {
$method_form_element_row_id_exists = TRUE;
}
// If the field provides a views form, allow it to modify the $form array.
$has_form = FALSE;
if (property_exists($field, 'views_form_callback')) {
$callback = $field->views_form_callback;
$callback($view, $field, $form, $form_state);
$has_form = TRUE;
}
elseif (method_exists($field, 'viewsForm')) {
$field->viewsForm($form, $form_state);
$has_form = TRUE;
}
// Build the substitutions array for use in the theme function.
if ($has_form) {
foreach ($view->result as $row_id => $row) {
if ($method_form_element_row_id_exists) {
$form_element_row_id = $field->form_element_row_id($row_id);
}
else {
$form_element_row_id = $row_id;
}
$substitutions[] = array(
'placeholder' => '<!--form-item-' . $form_element_name . '--' . $form_element_row_id . '-->',
'field_name' => $form_element_name,
'row_id' => $form_element_row_id,
);
}
}
}
// Give the area handlers a chance to extend the form.
$area_handlers = array_merge(array_values($view->header), array_values($view->footer));
$empty = empty($view->result);
foreach ($area_handlers as $area) {
if (method_exists($area, 'viewsForm') && !$area->viewsFormEmpty($empty)) {
$area->viewsForm($form, $form_state);
}
}
$form['#substitutions'] = array(
'#type' => 'value',
'#value' => $substitutions,
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->getBuildInfo()['args'][0];
// Call the validation method on every field handler that has it.
foreach ($view->field as $field) {
if (method_exists($field, 'viewsFormValidate')) {
$field->viewsFormValidate($form, $form_state);
}
}
// Call the validate method on every area handler that has it.
foreach (array('header', 'footer') as $area) {
foreach ($view->{$area} as $area_handler) {
if (method_exists($area_handler, 'viewsFormValidate')) {
$area_handler->viewsFormValidate($form, $form_state);
}
}
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$view = $form_state->getBuildInfo()['args'][0];
// Call the submit method on every field handler that has it.
foreach ($view->field as $field) {
if (method_exists($field, 'viewsFormSubmit')) {
$field->viewsFormSubmit($form, $form_state);
}
}
// Call the submit method on every area handler that has it.
foreach (array('header', 'footer') as $area) {
foreach ($view->{$area} as $area_handler) {
if (method_exists($area_handler, 'viewsFormSubmit')) {
$area_handler->viewsFormSubmit($form, $form_state);
}
}
}
}
}

View file

@ -0,0 +1,339 @@
<?php
namespace Drupal\views;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\HandlerBase;
/**
* This many to one helper object is used on both arguments and filters.
*
* @todo This requires extensive documentation on how this class is to
* be used. For now, look at the arguments and filters that use it. Lots
* of stuff is just pass-through but there are definitely some interesting
* areas where they interact.
*
* Any handler that uses this can have the following possibly additional
* definition terms:
* - numeric: If true, treat this field as numeric, using %d instead of %s in
* queries.
*/
class ManyToOneHelper {
function __construct($handler) {
$this->handler = $handler;
}
public static function defineOptions(&$options) {
$options['reduce_duplicates'] = array('default' => FALSE);
}
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$form['reduce_duplicates'] = array(
'#type' => 'checkbox',
'#title' => t('Reduce duplicates'),
'#description' => t("This filter can cause items that have more than one of the selected options to appear as duplicate results. If this filter causes duplicate results to occur, this checkbox can reduce those duplicates; however, the more terms it has to search for, the less performant the query will be, so use this with caution. Shouldn't be set on single-value fields, as it may cause values to disappear from display, if used on an incompatible field."),
'#default_value' => !empty($this->handler->options['reduce_duplicates']),
'#weight' => 4,
);
}
/**
* Sometimes the handler might want us to use some kind of formula, so give
* it that option. If it wants us to do this, it must set $helper->formula = TRUE
* and implement handler->getFormula();
*/
public function getField() {
if (!empty($this->formula)) {
return $this->handler->getFormula();
}
else {
return $this->handler->tableAlias . '.' . $this->handler->realField;
}
}
/**
* Add a table to the query.
*
* This is an advanced concept; not only does it add a new instance of the table,
* but it follows the relationship path all the way down to the relationship
* link point and adds *that* as a new relationship and then adds the table to
* the relationship, if necessary.
*/
public function addTable($join = NULL, $alias = NULL) {
// This is used for lookups in the many_to_one table.
$field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
if (empty($join)) {
$join = $this->getJoin();
}
// See if there's a chain between us and the base relationship. If so, we need
// to create a new relationship to use.
$relationship = $this->handler->relationship;
// Determine the primary table to seek
if (empty($this->handler->query->relationships[$relationship])) {
$base_table = $this->handler->view->storage->get('base_table');
}
else {
$base_table = $this->handler->query->relationships[$relationship]['base'];
}
// Cycle through the joins. This isn't as error-safe as the normal
// ensurePath logic. Perhaps it should be.
$r_join = clone $join;
while ($r_join->leftTable != $base_table) {
$r_join = HandlerBase::getTableJoin($r_join->leftTable, $base_table);
}
// If we found that there are tables in between, add the relationship.
if ($r_join->table != $join->table) {
$relationship = $this->handler->query->addRelationship($this->handler->table . '_' . $r_join->table, $r_join, $r_join->table, $this->handler->relationship);
}
// And now add our table, using the new relationship if one was used.
$alias = $this->handler->query->addTable($this->handler->table, $relationship, $join, $alias);
// Store what values are used by this table chain so that other chains can
// automatically discard those values.
if (empty($this->handler->view->many_to_one_tables[$field])) {
$this->handler->view->many_to_one_tables[$field] = $this->handler->value;
}
else {
$this->handler->view->many_to_one_tables[$field] = array_merge($this->handler->view->many_to_one_tables[$field], $this->handler->value);
}
return $alias;
}
public function getJoin() {
return $this->handler->getJoin();
}
/**
* Provide the proper join for summary queries. This is important in part because
* it will cooperate with other arguments if possible.
*/
public function summaryJoin() {
$field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
$join = $this->getJoin();
// shortcuts
$options = $this->handler->options;
$view = $this->handler->view;
$query = $this->handler->query;
if (!empty($options['require_value'])) {
$join->type = 'INNER';
}
if (empty($options['add_table']) || empty($view->many_to_one_tables[$field])) {
return $query->ensureTable($this->handler->table, $this->handler->relationship, $join);
}
else {
if (!empty($view->many_to_one_tables[$field])) {
foreach ($view->many_to_one_tables[$field] as $value) {
$join->extra = array(
array(
'field' => $this->handler->realField,
'operator' => '!=',
'value' => $value,
'numeric' => !empty($this->definition['numeric']),
),
);
}
}
return $this->addTable($join);
}
}
/**
* Override ensureMyTable so we can control how this joins in.
* The operator actually has influence over joining.
*/
public function ensureMyTable() {
if (!isset($this->handler->tableAlias)) {
// Case 1: Operator is an 'or' and we're not reducing duplicates.
// We hence get the absolute simplest:
$field = $this->handler->relationship . '_' . $this->handler->table . '.' . $this->handler->field;
if ($this->handler->operator == 'or' && empty($this->handler->options['reduce_duplicates'])) {
if (empty($this->handler->options['add_table']) && empty($this->handler->view->many_to_one_tables[$field])) {
// query optimization, INNER joins are slightly faster, so use them
// when we know we can.
$join = $this->getJoin();
if (isset($join)) {
$join->type = 'INNER';
}
$this->handler->tableAlias = $this->handler->query->ensureTable($this->handler->table, $this->handler->relationship, $join);
$this->handler->view->many_to_one_tables[$field] = $this->handler->value;
}
else {
$join = $this->getJoin();
$join->type = 'LEFT';
if (!empty($this->handler->view->many_to_one_tables[$field])) {
foreach ($this->handler->view->many_to_one_tables[$field] as $value) {
$join->extra = array(
array(
'field' => $this->handler->realField,
'operator' => '!=',
'value' => $value,
'numeric' => !empty($this->handler->definition['numeric']),
),
);
}
}
$this->handler->tableAlias = $this->addTable($join);
}
return $this->handler->tableAlias;
}
// Case 2: it's an 'and' or an 'or'.
// We do one join per selected value.
if ($this->handler->operator != 'not') {
// Clone the join for each table:
$this->handler->tableAliases = array();
foreach ($this->handler->value as $value) {
$join = $this->getJoin();
if ($this->handler->operator == 'and') {
$join->type = 'INNER';
}
$join->extra = array(
array(
'field' => $this->handler->realField,
'value' => $value,
'numeric' => !empty($this->handler->definition['numeric']),
),
);
// The table alias needs to be unique to this value across the
// multiple times the filter or argument is called by the view.
if (!isset($this->handler->view->many_to_one_aliases[$field][$value])) {
if (!isset($this->handler->view->many_to_one_count[$this->handler->table])) {
$this->handler->view->many_to_one_count[$this->handler->table] = 0;
}
$this->handler->view->many_to_one_aliases[$field][$value] = $this->handler->table . '_value_' . ($this->handler->view->many_to_one_count[$this->handler->table]++);
}
$this->handler->tableAliases[$value] = $this->addTable($join, $this->handler->view->many_to_one_aliases[$field][$value]);
// Set tableAlias to the first of these.
if (empty($this->handler->tableAlias)) {
$this->handler->tableAlias = $this->handler->tableAliases[$value];
}
}
}
// Case 3: it's a 'not'.
// We just do one join. We'll add a where clause during
// the query phase to ensure that $table.$field IS NULL.
else {
$join = $this->getJoin();
$join->type = 'LEFT';
$join->extra = array();
$join->extraOperator = 'OR';
foreach ($this->handler->value as $value) {
$join->extra[] = array(
'field' => $this->handler->realField,
'value' => $value,
'numeric' => !empty($this->handler->definition['numeric']),
);
}
$this->handler->tableAlias = $this->addTable($join);
}
}
return $this->handler->tableAlias;
}
/**
* Provides a unique placeholders for handlers.
*/
protected function placeholder() {
return $this->handler->query->placeholder($this->handler->options['table'] . '_' . $this->handler->options['field']);
}
public function addFilter() {
if (empty($this->handler->value)) {
return;
}
$this->handler->ensureMyTable();
// Shorten some variables:
$field = $this->getField();
$options = $this->handler->options;
$operator = $this->handler->operator;
$formula = !empty($this->formula);
$value = $this->handler->value;
if (empty($options['group'])) {
$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).
$add_condition = TRUE;
if ($operator == 'not') {
$value = NULL;
$operator = 'IS NULL';
$add_condition = FALSE;
}
elseif ($operator == 'or' && empty($options['reduce_duplicates'])) {
if (count($value) > 1) {
$operator = 'IN';
}
else {
$value = is_array($value) ? array_pop($value) : $value;
$operator = '=';
}
$add_condition = FALSE;
}
if (!$add_condition) {
if ($formula) {
$placeholder = $this->placeholder();
if ($operator == 'IN') {
$operator = "$operator IN($placeholder)";
}
else {
$operator = "$operator $placeholder";
}
$placeholders = array(
$placeholder => $value,
);
$this->handler->query->addWhereExpression($options['group'], "$field $operator", $placeholders);
}
else {
$placeholder = $this->placeholder();
if (count($this->handler->value) > 1) {
$placeholder .= '[]';
if ($operator == 'IS NULL') {
$this->handler->query->addWhereExpression(0, "$field $operator");
}
else {
$this->handler->query->addWhereExpression(0, "$field $operator($placeholder)", array($placeholder => $value));
}
}
else {
if ($operator == 'IS NULL') {
$this->handler->query->addWhereExpression(0, "$field $operator");
}
else {
$this->handler->query->addWhereExpression(0, "$field $operator $placeholder", array($placeholder => $value));
}
}
}
}
if ($add_condition) {
$field = $this->handler->realField;
$clause = $operator == 'or' ? db_or() : db_and();
foreach ($this->handler->tableAliases as $value => $alias) {
$clause->condition("$alias.$field", $value);
}
// implode on either AND or OR.
$this->handler->query->addWhere($options['group'], $clause);
}
}
}

View file

@ -0,0 +1,128 @@
<?php
namespace Drupal\views\Plugin\Block;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Element\View;
/**
* Provides a generic Views block.
*
* @Block(
* id = "views_block",
* admin_label = @Translation("Views Block"),
* deriver = "Drupal\views\Plugin\Derivative\ViewsBlock"
* )
*/
class ViewsBlock extends ViewsBlockBase {
/**
* {@inheritdoc}
*/
public function build() {
$this->view->display_handler->preBlockBuild($this);
// We ask ViewExecutable::buildRenderable() to avoid creating a render cache
// entry for the view output by passing FALSE, because we're going to cache
// the whole block instead.
if ($output = $this->view->buildRenderable($this->displayID, [], FALSE)) {
// Before returning the block output, convert it to a renderable array
// with contextual links.
$this->addContextualLinks($output);
// Block module expects to get a final render array, without another
// top-level #pre_render callback. So, here we make sure that Views'
// #pre_render callback has already been applied.
$output = View::preRenderViewElement($output);
// Override the label to the dynamic title configured in the view.
if (empty($this->configuration['views_label']) && $this->view->getTitle()) {
$output['#title'] = ['#markup' => $this->view->getTitle(), '#allowed_tags' => Xss::getHtmlTagList()];
}
// When view_build is empty, the actual render array output for this View
// is going to be empty. In that case, return just #cache, so that the
// render system knows the reasons (cache contexts & tags) why this Views
// block is empty, and can cache it accordingly.
if (empty($output['view_build'])) {
$output = ['#cache' => $output['#cache']];
}
return $output;
}
return array();
}
/**
* {@inheritdoc}
*/
public function getConfiguration() {
$configuration = parent::getConfiguration();
// Set the label to the static title configured in the view.
if (!empty($configuration['views_label'])) {
$configuration['label'] = $configuration['views_label'];
}
return $configuration;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
$settings = parent::defaultConfiguration();
if ($this->displaySet) {
$settings += $this->view->display_handler->blockSettings($settings);
}
// Set custom cache settings.
if (isset($this->pluginDefinition['cache'])) {
$settings['cache'] = $this->pluginDefinition['cache'];
}
return $settings;
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
if ($this->displaySet) {
return $this->view->display_handler->blockForm($this, $form, $form_state);
}
return array();
}
/**
* {@inheritdoc}
*/
public function blockValidate($form, FormStateInterface $form_state) {
if ($this->displaySet) {
$this->view->display_handler->blockValidate($this, $form, $form_state);
}
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
parent::blockSubmit($form, $form_state);
if ($this->displaySet) {
$this->view->display_handler->blockSubmit($this, $form, $form_state);
}
}
/**
* {@inheritdoc}
*/
public function getMachineNameSuggestion() {
$this->view->setDisplay($this->displayID);
return 'views_block__' . $this->view->storage->id() . '_' . $this->view->current_display;
}
}

View file

@ -0,0 +1,209 @@
<?php
namespace Drupal\views\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\views\ViewExecutableFactory;
use Drupal\Core\Entity\EntityStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Base class for Views block plugins.
*/
abstract class ViewsBlockBase extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The View executable object.
*
* @var \Drupal\views\ViewExecutable
*/
protected $view;
/**
* The display ID being used for this View.
*
* @var string
*/
protected $displayID;
/**
* Indicates whether the display was successfully set.
*
* @var bool
*/
protected $displaySet;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $user;
/**
* Constructs a \Drupal\views\Plugin\Block\ViewsBlockBase 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\views\ViewExecutableFactory $executable_factory
* The view executable factory.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The views storage.
* @param \Drupal\Core\Session\AccountInterface $user
* The current user.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, ViewExecutableFactory $executable_factory, EntityStorageInterface $storage, AccountInterface $user) {
$this->pluginId = $plugin_id;
$delta = $this->getDerivativeId();
list($name, $this->displayID) = explode('-', $delta, 2);
// Load the view.
$view = $storage->load($name);
$this->view = $executable_factory->get($view);
$this->displaySet = $this->view->setDisplay($this->displayID);
$this->user = $user;
parent::__construct($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration, $plugin_id, $plugin_definition,
$container->get('views.executable'),
$container->get('entity.manager')->getStorage('view'),
$container->get('current_user')
);
}
/**
* {@inheritdoc}
*/
protected function blockAccess(AccountInterface $account) {
if ($this->view->access($this->displayID)) {
$access = AccessResult::allowed();
}
else {
$access = AccessResult::forbidden();
}
return $access;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return array('views_label' => '');
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
// Set the default label to '' so the views internal title is used.
$form['label']['#default_value'] = '';
$form['label']['#access'] = FALSE;
// Unset the machine_name provided by BlockForm.
unset($form['id']['#machine_name']['source']);
// Prevent users from changing the auto-generated block machine_name.
$form['id']['#access'] = FALSE;
$form['#pre_render'][] = '\Drupal\views\Plugin\views\PluginBase::preRenderAddFieldsetMarkup';
// Allow to override the label on the actual page.
$form['views_label_checkbox'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Override title'),
'#default_value' => !empty($this->configuration['views_label']),
);
$form['views_label_fieldset'] = array(
'#type' => 'fieldset',
'#states' => array(
'visible' => array(
array(
':input[name="settings[views_label_checkbox]"]' => array('checked' => TRUE),
),
),
),
);
$form['views_label'] = array(
'#title' => $this->t('Title'),
'#type' => 'textfield',
'#default_value' => $this->configuration['views_label'] ?: $this->view->getTitle(),
'#states' => array(
'visible' => array(
array(
':input[name="settings[views_label_checkbox]"]' => array('checked' => TRUE),
),
),
),
'#fieldset' => 'views_label_fieldset',
);
if ($this->view->storage->access('edit') && \Drupal::moduleHandler()->moduleExists('views_ui')) {
$form['views_label']['#description'] = $this->t('Changing the title here means it cannot be dynamically altered anymore. (Try changing it directly in <a href=":url">@name</a>.)', array(':url' => \Drupal::url('entity.view.edit_display_form', array('view' => $this->view->storage->id(), 'display_id' => $this->displayID)), '@name' => $this->view->storage->label()));
}
else {
$form['views_label']['#description'] = $this->t('Changing the title here means it cannot be dynamically altered anymore.');
}
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
if (!$form_state->isValueEmpty('views_label_checkbox')) {
$this->configuration['views_label'] = $form_state->getValue('views_label');
}
else {
$this->configuration['views_label'] = '';
}
$form_state->unsetValue('views_label_checkbox');
}
/**
* Converts Views block content to a renderable array with contextual links.
*
* @param string|array $output
* An string|array representing the block. This will be modified to be a
* renderable array, containing the optional '#contextual_links' property (if
* there are any contextual links associated with the block).
* @param string $block_type
* The type of the block. If it's 'block' it's a regular views display,
* but 'exposed_filter' exist as well.
*/
protected function addContextualLinks(&$output, $block_type = 'block') {
// Do not add contextual links to an empty block.
if (!empty($output)) {
// Contextual links only work on blocks whose content is a renderable
// array, so if the block contains a string of already-rendered markup,
// convert it to an array.
if (is_string($output)) {
$output = array('#markup' => $output);
}
// views_add_contextual_links() needs the following information in
// order to be attached to the view.
$output['#view_id'] = $this->view->storage->id();
$output['#view_display_show_admin_links'] = $this->view->getShowAdminLinks();
$output['#view_display_plugin_id'] = $this->view->display_handler->getPluginId();
views_add_contextual_links($output, $block_type, $this->displayID);
}
}
}

View file

@ -0,0 +1,38 @@
<?php
namespace Drupal\views\Plugin\Block;
use Drupal\Core\Cache\Cache;
/**
* Provides a 'Views Exposed Filter' block.
*
* @Block(
* id = "views_exposed_filter_block",
* admin_label = @Translation("Views Exposed Filter Block"),
* deriver = "Drupal\views\Plugin\Derivative\ViewsExposedFilterBlock"
* )
*/
class ViewsExposedFilterBlock extends ViewsBlockBase {
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
$contexts = $this->view->display_handler->getCacheMetadata()->getCacheContexts();
return Cache::mergeContexts(parent::getCacheContexts(), $contexts);
}
/**
* {@inheritdoc}
*/
public function build() {
$output = $this->view->display_handler->viewExposedFormBlocks();
// Before returning the block output, convert it to a renderable array with
// contextual links.
$this->addContextualLinks($output, 'exposed_filter');
return $output;
}
}

View file

@ -0,0 +1,35 @@
<?php
namespace Drupal\views\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\views\Views;
/**
* A derivative class which provides automatic wizards for all base tables.
*
* The derivatives store all base table plugin information.
*/
class DefaultWizardDeriver extends DeriverBase {
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$views_data = Views::viewsData();
$base_tables = array_keys($views_data->fetchBaseTables());
$this->derivatives = array();
foreach ($base_tables as $table) {
$views_info = $views_data->get($table);
if (empty($views_info['table']['wizard_id'])) {
$this->derivatives[$table] = array(
'id' => 'standard',
'base_table' => $table,
'title' => $views_info['table']['base']['title'],
'class' => 'Drupal\views\Plugin\views\wizard\Standard'
);
}
}
return parent::getDerivativeDefinitions($base_plugin_definition);
}
}

View file

@ -0,0 +1,119 @@
<?php
namespace Drupal\views\Plugin\Derivative;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides block plugin definitions for all Views block displays.
*
* @see \Drupal\views\Plugin\Block\ViewsBlock
*/
class ViewsBlock implements ContainerDeriverInterface {
/**
* List of derivative definitions.
*
* @var array
*/
protected $derivatives = array();
/**
* The base plugin ID.
*
* @var string
*/
protected $basePluginId;
/**
* The view storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $viewStorage;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('entity.manager')->getStorage('view')
);
}
/**
* Constructs a ViewsBlock object.
*
* @param string $base_plugin_id
* The base plugin ID.
* @param \Drupal\Core\Entity\EntityStorageInterface $view_storage
* The entity storage to load views.
*/
public function __construct($base_plugin_id, EntityStorageInterface $view_storage) {
$this->basePluginId = $base_plugin_id;
$this->viewStorage = $view_storage;
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
return $this->derivatives[$derivative_id];
}
$this->getDerivativeDefinitions($base_plugin_definition);
return $this->derivatives[$derivative_id];
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
// Check all Views for block displays.
foreach ($this->viewStorage->loadMultiple() as $view) {
// Do not return results for disabled views.
if (!$view->status()) {
continue;
}
$executable = $view->getExecutable();
$executable->initDisplay();
foreach ($executable->displayHandlers as $display) {
// Add a block plugin definition for each block display.
if (isset($display) && !empty($display->definition['uses_hook_block'])) {
$delta = $view->id() . '-' . $display->display['id'];
$admin_label = $display->getOption('block_description');
if (empty($admin_label)) {
if ($display->display['display_title'] == $display->definition['title']) {
$admin_label = $view->label();
}
else {
// Allow translators to control the punctuation. Plugin
// definitions get cached, so use TranslatableMarkup() instead of
// t() to avoid double escaping when $admin_label is rendered
// during requests that use the cached definition.
$admin_label = new TranslatableMarkup('@view: @display', ['@view' => $view->label(), '@display' => $display->display['display_title']]);
}
}
$this->derivatives[$delta] = array(
'category' => $display->getOption('block_category'),
'admin_label' => $admin_label,
'config_dependencies' => array(
'config' => array(
$view->getConfigDependencyName(),
)
)
);
$this->derivatives[$delta] += $base_plugin_definition;
}
}
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,90 @@
<?php
namespace Drupal\views\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides views argument validator plugin definitions for all entity types.
*
* @ingroup views_argument_validator_plugins
*
* @see \Drupal\views\Plugin\views\argument_validator\Entity
*/
class ViewsEntityArgumentValidator extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The base plugin ID this derivative is for.
*
* @var string
*/
protected $basePluginId;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* List of derivative definitions.
*
* @var array
*/
protected $derivatives = array();
/**
* Constructs an ViewsEntityArgumentValidator object.
*
* @param string $base_plugin_id
* The base plugin ID.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The string translation.
*/
public function __construct($base_plugin_id, EntityManagerInterface $entity_manager, TranslationInterface $string_translation) {
$this->basePluginId = $base_plugin_id;
$this->entityManager = $entity_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('entity.manager'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$entity_types = $this->entityManager->getDefinitions();
$this->derivatives = array();
foreach ($entity_types as $entity_type_id => $entity_type) {
$this->derivatives[$entity_type_id] = array(
'id' => 'entity:' . $entity_type_id,
'provider' => 'views',
'title' => $entity_type->getLabel(),
'help' => $this->t('Validate @label', array('@label' => $entity_type->getLabel())),
'entity_type' => $entity_type_id,
'class' => $base_plugin_definition['class'],
);
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,108 @@
<?php
namespace Drupal\views\Plugin\Derivative;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\views\ViewsData;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides views row plugin definitions for all non-special entity types.
*
* @ingroup views_row_plugins
*
* @see \Drupal\views\Plugin\views\row\EntityRow
*/
class ViewsEntityRow implements ContainerDeriverInterface {
/**
* Stores all entity row plugin information.
*
* @var array
*/
protected $derivatives = array();
/**
* The base plugin ID that the derivative is for.
*
* @var string
*/
protected $basePluginId;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The views data service.
*
* @var \Drupal\views\ViewsData
*/
protected $viewsData;
/**
* Constructs a ViewsEntityRow object.
*
* @param string $base_plugin_id
* The base plugin ID.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\views\ViewsData $views_data
* The views data service.
*/
public function __construct($base_plugin_id, EntityManagerInterface $entity_manager, ViewsData $views_data) {
$this->basePluginId = $base_plugin_id;
$this->entityManager = $entity_manager;
$this->viewsData = $views_data;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('entity.manager'),
$container->get('views.views_data')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
return $this->derivatives[$derivative_id];
}
$this->getDerivativeDefinitions($base_plugin_definition);
return $this->derivatives[$derivative_id];
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->entityManager->getDefinitions() as $entity_type_id => $entity_type) {
// Just add support for entity types which have a views integration.
if (($base_table = $entity_type->getBaseTable()) && $this->viewsData->get($base_table) && $this->entityManager->hasHandler($entity_type_id, 'view_builder')) {
$this->derivatives[$entity_type_id] = array(
'id' => 'entity:' . $entity_type_id,
'provider' => 'views',
'title' => $entity_type->getLabel(),
'help' => t('Display the @label', array('@label' => $entity_type->getLabel())),
'base' => array($entity_type->getDataTable() ?: $entity_type->getBaseTable()),
'entity_type' => $entity_type_id,
'display_types' => array('normal'),
'class' => $base_plugin_definition['class'],
);
}
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace Drupal\views\Plugin\Derivative;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides block plugin definitions for all Views exposed filters.
*
* @see \Drupal\views\Plugin\Block\ViewsExposedFilterBlock
*/
class ViewsExposedFilterBlock implements ContainerDeriverInterface {
/**
* List of derivative definitions.
*
* @var array
*/
protected $derivatives = array();
/**
* The view storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $viewStorage;
/**
* The base plugin ID that the derivative is for.
*
* @var string
*/
protected $basePluginId;
/**
* Constructs a ViewsExposedFilterBlock object.
*
* @param string $base_plugin_id
* The base plugin ID.
* @param \Drupal\Core\Entity\EntityStorageInterface $view_storage
* The entity storage to load views.
*/
public function __construct($base_plugin_id, EntityStorageInterface $view_storage) {
$this->basePluginId = $base_plugin_id;
$this->viewStorage = $view_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('entity.manager')->getStorage('view')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
return $this->derivatives[$derivative_id];
}
$this->getDerivativeDefinitions($base_plugin_definition);
return $this->derivatives[$derivative_id];
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
// Check all Views for displays with an exposed filter block.
foreach ($this->viewStorage->loadMultiple() as $view) {
// Do not return results for disabled views.
if (!$view->status()) {
continue;
}
$executable = $view->getExecutable();
$executable->initDisplay();
foreach ($executable->displayHandlers as $display) {
if (isset($display) && $display->getOption('exposed_block')) {
// Add a block definition for the block.
if ($display->usesExposedFormInBlock()) {
$delta = $view->id() . '-' . $display->display['id'];
$desc = t('Exposed form: @view-@display_id', array('@view' => $view->id(), '@display_id' => $display->display['id']));
$this->derivatives[$delta] = array(
'admin_label' => $desc,
'config_dependencies' => array(
'config' => array(
$view->getConfigDependencyName(),
)
)
);
$this->derivatives[$delta] += $base_plugin_definition;
}
}
}
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,165 @@
<?php
namespace Drupal\views\Plugin\Derivative;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides local task definitions for all views configured as local tasks.
*/
class ViewsLocalTask extends DeriverBase implements ContainerDeriverInterface {
/**
* The route provider.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* The state key value store.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The view storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $viewStorage;
/**
* Constructs a \Drupal\views\Plugin\Derivative\ViewsLocalTask instance.
*
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider.
* @param \Drupal\Core\State\StateInterface $state
* The state key value store.
* @param \Drupal\Core\Entity\EntityStorageInterface $view_storage
* The view storage.
*/
public function __construct(RouteProviderInterface $route_provider, StateInterface $state, EntityStorageInterface $view_storage) {
$this->routeProvider = $route_provider;
$this->state = $state;
$this->viewStorage = $view_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('router.route_provider'),
$container->get('state'),
$container->get('entity.manager')->getStorage('view')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$this->derivatives = array();
$view_route_names = $this->state->get('views.view_route_names');
foreach ($this->getApplicableMenuViews() as $pair) {
/** @var $executable \Drupal\views\ViewExecutable */
list($view_id, $display_id) = $pair;
$executable = $this->viewStorage->load($view_id)->getExecutable();
$executable->setDisplay($display_id);
$menu = $executable->display_handler->getOption('menu');
if (in_array($menu['type'], array('tab', 'default tab'))) {
$plugin_id = 'view.' . $executable->storage->id() . '.' . $display_id;
$route_name = $view_route_names[$executable->storage->id() . '.' . $display_id];
// Don't add a local task for views which override existing routes.
// @todo Alternative it could just change the existing entry.
if ($route_name != $plugin_id) {
continue;
}
$this->derivatives[$plugin_id] = array(
'route_name' => $route_name,
'weight' => $menu['weight'],
'title' => $menu['title'],
) + $base_plugin_definition;
// Default local tasks have themselves as root tab.
if ($menu['type'] == 'default tab') {
$this->derivatives[$plugin_id]['base_route'] = $route_name;
}
}
}
return $this->derivatives;
}
/**
* Alters base_route and parent_id into the views local tasks.
*/
public function alterLocalTasks(&$local_tasks) {
$view_route_names = $this->state->get('views.view_route_names');
foreach ($this->getApplicableMenuViews() as $pair) {
list($view_id, $display_id) = $pair;
/** @var $executable \Drupal\views\ViewExecutable */
$executable = $this->viewStorage->load($view_id)->getExecutable();
$executable->setDisplay($display_id);
$menu = $executable->display_handler->getOption('menu');
// We already have set the base_route for default tabs.
if (in_array($menu['type'], array('tab'))) {
$plugin_id = 'view.' . $executable->storage->id() . '.' . $display_id;
$view_route_name = $view_route_names[$executable->storage->id() . '.' . $display_id];
// Don't add a local task for views which override existing routes.
if ($view_route_name != $plugin_id) {
unset($local_tasks[$plugin_id]);
continue;
}
// Find out the parent route.
// @todo Find out how to find both the root and parent tab.
$path = $executable->display_handler->getPath();
$split = explode('/', $path);
array_pop($split);
$path = implode('/', $split);
$pattern = '/' . str_replace('%', '{}', $path);
if ($routes = $this->routeProvider->getRoutesByPattern($pattern)) {
foreach ($routes->all() as $name => $route) {
$local_tasks['views_view:' . $plugin_id]['base_route'] = $name;
// Skip after the first found route.
break;
}
}
}
}
}
/**
* Return a list of all views and display IDs that have a menu entry.
*
* @return array
* A list of arrays containing the $view and $display_id.
* @code
* array(
* array($view, $display_id),
* array($view, $display_id),
* );
* @endcode
*/
protected function getApplicableMenuViews() {
return Views::getApplicableViews('uses_menu_links');
}
}

View file

@ -0,0 +1,69 @@
<?php
namespace Drupal\views\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\views\Plugin\views\display\DisplayMenuInterface;
use Drupal\views\Views;
use Drupal\Core\Entity\EntityStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides menu links for Views.
*
* @see \Drupal\views\Plugin\Menu\ViewsMenuLink
*/
class ViewsMenuLink extends DeriverBase implements ContainerDeriverInterface {
/**
* The view storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $viewStorage;
/**
* Constructs a \Drupal\views\Plugin\Derivative\ViewsLocalTask instance.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $view_storage
* The view storage.
*/
public function __construct(EntityStorageInterface $view_storage) {
$this->viewStorage = $view_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity.manager')->getStorage('view')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
$links = array();
$views = Views::getApplicableViews('uses_menu_links');
foreach ($views as $data) {
list($view_id, $display_id) = $data;
/** @var \Drupal\views\ViewExecutable $executable */
$executable = $this->viewStorage->load($view_id)->getExecutable();
$executable->initDisplay();
$display = $executable->displayHandlers->get($display_id);
if (($display instanceof DisplayMenuInterface) && ($result = $display->getMenuLinks())) {
foreach ($result as $link_id => $link) {
$links[$link_id] = $link + $base_plugin_definition;
}
}
}
return $links;
}
}

View file

@ -0,0 +1,291 @@
<?php
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\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.
*
* @EntityReferenceSelection(
* id = "views",
* label = @Translation("Views: Filter by an entity reference view"),
* group = "views",
* weight = 0
* )
*/
class ViewsSelection extends PluginBase implements SelectionInterface, 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')
);
}
/**
* The loaded View object.
*
* @var \Drupal\views\ViewExecutable;
*/
protected $view;
/**
* {@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'] : array();
$displays = Views::getApplicableViews('entity_reference_display');
// Filter views that list the entity type we want, and group the separate
// displays by view.
$entity_type = $this->entityManager->getDefinition($this->configuration['target_type']);
$view_storage = $this->entityManager->getStorage('view');
$options = array();
foreach ($displays as $data) {
list($view_id, $display_id) = $data;
$view = $view_storage->load($view_id);
if (in_array($view->get('base_table'), [$entity_type->getBaseTable(), $entity_type->getDataTable()])) {
$display = $view->get('display');
$options[$view_id . ':' . $display_id] = $view_id . ' - ' . $display[$display_id]['display_title'];
}
}
// The value of the 'view_and_display' select below will need to be split
// into 'view_name' and 'view_display' in the final submitted values, so
// we massage the data at validate time on the wrapping element (not
// ideal).
$form['view']['#element_validate'] = array(array(get_called_class(), 'settingsFormValidate'));
if ($options) {
$default = !empty($view_settings['view_name']) ? $view_settings['view_name'] . ':' . $view_settings['display_name'] : NULL;
$form['view']['view_and_display'] = array(
'#type' => 'select',
'#title' => $this->t('View used to select the entities'),
'#required' => TRUE,
'#options' => $options,
'#default_value' => $default,
'#description' => '<p>' . $this->t('Choose the view and display that select the entities that can be referenced.<br />Only views with a display of type "Entity Reference" are eligible.') . '</p>',
);
$default = !empty($view_settings['arguments']) ? implode(', ', $view_settings['arguments']) : '';
$form['view']['arguments'] = array(
'#type' => 'textfield',
'#title' => $this->t('View arguments'),
'#default_value' => $default,
'#required' => FALSE,
'#description' => $this->t('Provide a comma separated list of arguments to pass to the view.'),
);
}
else {
if ($this->currentUser->hasPermission('administer views') && $this->moduleHandler->moduleExists('views_ui')) {
$form['view']['no_view_help'] = array(
'#markup' => '<p>' . $this->t('No eligible views were found. <a href=":create">Create a view</a> with an <em>Entity Reference</em> display, or add such a display to an <a href=":existing">existing view</a>.', array(
':create' => Url::fromRoute('views_ui.add')->toString(),
':existing' => Url::fromRoute('entity.view.collection')->toString(),
)) . '</p>',
);
}
else {
$form['view']['no_view_help']['#markup'] = '<p>' . $this->t('No eligible views were found.') . '</p>';
}
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { }
/**
* Initializes a view.
*
* @param string|null $match
* (Optional) Text to match the label against. Defaults to NULL.
* @param string $match_operator
* (Optional) The operation the matching should be done with. Defaults
* to "CONTAINS".
* @param int $limit
* Limit the query to a given number of items. Defaults to 0, which
* indicates no limiting.
* @param array|null $ids
* Array of entity IDs. Defaults to NULL.
*
* @return bool
* 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'];
// 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.', array('%view_name' => $view_name)), 'warning');
return FALSE;
}
$this->view->setDisplay($display_name);
// Pass options to the display handler to make them available later.
$entity_reference_options = array(
'match' => $match,
'match_operator' => $match_operator,
'limit' => $limit,
'ids' => $ids,
);
$this->view->displayHandlers->get($display_name)->setOption('entity_reference_options', $entity_reference_options);
return TRUE;
}
/**
* {@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'];
$result = array();
if ($this->initializeView($match, $match_operator, $limit)) {
// Get the results.
$result = $this->view->executeDisplay($display_name, $arguments);
}
$return = array();
if ($result) {
foreach ($this->view->result as $row) {
$entity = $row->_entity;
$return[$entity->bundle()][$entity->id()] = $entity->label();
}
}
return $return;
}
/**
* {@inheritdoc}
*/
public function countReferenceableEntities($match = NULL, $match_operator = 'CONTAINS') {
$this->getReferenceableEntities($match, $match_operator);
return $this->view->pager->getTotalItems();
}
/**
* {@inheritdoc}
*/
public function validateReferenceableEntities(array $ids) {
$handler_settings = $this->configuration['handler_settings'];
$display_name = $handler_settings['view']['display_name'];
$arguments = $handler_settings['view']['arguments'];
$result = array();
if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) {
// Get the results.
$entities = $this->view->executeDisplay($display_name, $arguments);
$result = array_keys($entities);
}
return $result;
}
/**
* Element validate; Check View is valid.
*/
public static function settingsFormValidate($element, FormStateInterface $form_state, $form) {
// Split view name and display name from the 'view_and_display' value.
if (!empty($element['view_and_display']['#value'])) {
list($view, $display) = explode(':', $element['view_and_display']['#value']);
}
else {
$form_state->setError($element, t('The views entity selection mode requires a view.'));
return;
}
// Explode the 'arguments' string into an actual array. Beware, explode()
// turns an empty string into an array with one empty string. We'll need an
// empty array instead.
$arguments_string = trim($element['arguments']['#value']);
if ($arguments_string === '') {
$arguments = array();
}
else {
// array_map() is called to trim whitespaces from the arguments.
$arguments = array_map('trim', explode(',', $arguments_string));
}
$value = array('view_name' => $view, 'display_name' => $display, 'arguments' => $arguments);
$form_state->setValueForElement($element, $value);
}
/**
* {@inheritdoc}
*/
public function entityQueryAlter(SelectInterface $query) { }
}

View file

@ -0,0 +1,79 @@
<?php
namespace Drupal\views\Plugin\Menu\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Menu\Form\MenuLinkDefaultForm;
/**
* Provides a form to edit Views menu links.
*
* This provides the feature to edit the title and description, in contrast to
* the default menu link form.
*
* @see \Drupal\views\Plugin\Menu\ViewsMenuLink
*/
class ViewsMenuLinkForm extends MenuLinkDefaultForm {
/**
* The edited views menu link.
*
* @var \Drupal\views\Plugin\Menu\ViewsMenuLink
*/
protected $menuLink;
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
// Put the title field first.
$form['title'] = array(
'#type' => 'textfield',
'#title' => $this->t('Title'),
// @todo Ensure that the view is not loaded with a localized title.
// https://www.drupal.org/node/2309507
'#default_value' => $this->menuLink->getTitle(),
'#weight' => -10,
);
$form['description'] = array(
'#type' => 'textfield',
'#title' => $this->t('Description'),
'#description' => $this->t('Shown when hovering over the menu link.'),
// @todo Ensure that the view is not loaded with a localized description.
// https://www.drupal.org/node/2309507
'#default_value' => $this->menuLink->getDescription(),
'#weight' => -5,
);
$form += parent::buildConfigurationForm($form, $form_state);
$form['info']['#weight'] = -8;
$form['path']['#weight'] = -7;
$view = $this->menuLink->loadView();
$id = $view->storage->id();
$label = $view->storage->label();
if ($this->moduleHandler->moduleExists('views_ui')) {
$message = $this->t('This link is provided by the Views module. The path can be changed by editing the view <a href=":url">@label</a>', array(':url' => \Drupal::url('entity.view.edit_form', array('view' => $id)), '@label' => $label));
}
else {
$message = $this->t('This link is provided by the Views module from view %label.', array('%label' => $label));
}
$form['info']['#title'] = $message;
return $form;
}
/**
* {@inheritdoc}
*/
public function extractFormValues(array &$form, FormStateInterface $form_state) {
$definition = parent::extractFormValues($form, $form_state);
$definition['title'] = $form_state->getValue('title');
$definition['description'] = $form_state->getValue('description');
return $definition;
}
}

View file

@ -0,0 +1,170 @@
<?php
namespace Drupal\views\Plugin\Menu;
use Drupal\Core\Menu\MenuLinkBase;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\views\ViewExecutableFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines menu links provided by views.
*
* @see \Drupal\views\Plugin\Derivative\ViewsMenuLink
*/
class ViewsMenuLink extends MenuLinkBase implements ContainerFactoryPluginInterface {
/**
* {@inheritdoc}
*/
protected $overrideAllowed = array(
'menu_name' => 1,
'parent' => 1,
'weight' => 1,
'expanded' => 1,
'enabled' => 1,
'title' => 1,
'description' => 1,
);
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The view executable factory.
*
* @var \Drupal\views\ViewExecutableFactory
*/
protected $viewExecutableFactory;
/**
* The view executable of the menu link.
*
* @var \Drupal\views\ViewExecutable
*/
protected $view;
/**
* Constructs a new ViewsMenuLink.
*
* @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\views\ViewExecutableFactory $view_executable_factory
* The view executable factory
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager, ViewExecutableFactory $view_executable_factory) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
$this->viewExecutableFactory = $view_executable_factory;
}
/**
* {@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('views.executable')
);
}
/**
* Initializes the proper view.
*
* @return \Drupal\views\ViewExecutable
* The view executable.
*/
public function loadView() {
if (empty($this->view)) {
$metadata = $this->getMetaData();
$view_id = $metadata['view_id'];
$display_id = $metadata['display_id'];
$view_entity = $this->entityManager->getStorage('view')->load($view_id);
$view = $this->viewExecutableFactory->get($view_entity);
$view->setDisplay($display_id);
$view->initDisplay();
$this->view = $view;
}
return $this->view;
}
/**
* {@inheritdoc}
*/
public function getTitle() {
// @todo Get the translated value from the config without instantiating the
// view. https://www.drupal.org/node/2310379
return $this->loadView()->display_handler->getOption('menu')['title'];
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->loadView()->display_handler->getOption('menu')['description'];
}
/**
* {@inheritdoc}
*/
public function isExpanded() {
return (bool) $this->loadView()->display_handler->getOption('menu')['expanded'];
}
/**
* {@inheritdoc}
*/
public function updateLink(array $new_definition_values, $persist) {
$overrides = array_intersect_key($new_definition_values, $this->overrideAllowed);
// Update the definition.
$this->pluginDefinition = $overrides + $this->pluginDefinition;
if ($persist) {
$view = $this->loadView();
$display = &$view->storage->getDisplay($view->current_display);
// Just save the title to the original view.
$changed = FALSE;
foreach ($overrides as $key => $new_definition_value) {
if (empty($display['display_options']['menu'][$key]) || $display['display_options']['menu'][$key] != $new_definition_value) {
$display['display_options']['menu'][$key] = $new_definition_value;
$changed = TRUE;
}
}
if ($changed) {
// @todo Improve this to not trigger a full rebuild of everything, if we
// just changed some properties. https://www.drupal.org/node/2310389
$view->storage->save();
}
}
return $this->pluginDefinition;
}
/**
* {@inheritdoc}
*/
public function isDeletable() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function deleteLink() {
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace Drupal\views\Plugin;
use Drupal\Component\Plugin\FallbackPluginManagerInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\views\ViewsData;
use Symfony\Component\DependencyInjection\Container;
use Drupal\views\Plugin\views\HandlerBase;
/**
* Plugin type manager for all views handlers.
*/
class ViewsHandlerManager extends DefaultPluginManager implements FallbackPluginManagerInterface {
/**
* The views data cache.
*
* @var \Drupal\views\ViewsData
*/
protected $viewsData;
/**
* The handler type.
*
* @var string
*
* @see \Drupal\views\ViewExecutable::getHandlerTypes().
*/
protected $handlerType;
/**
* Constructs a ViewsHandlerManager object.
*
* @param string $handler_type
* The plugin type, for example filter.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
* @param \Drupal\views\ViewsData $views_data
* The views data cache.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct($handler_type, \Traversable $namespaces, ViewsData $views_data, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
$plugin_definition_annotation_name = 'Drupal\views\Annotation\Views' . Container::camelize($handler_type);
$plugin_interface = 'Drupal\views\Plugin\views\ViewsHandlerInterface';
if ($handler_type == 'join') {
$plugin_interface = 'Drupal\views\Plugin\views\join\JoinPluginInterface';
}
parent::__construct("Plugin/views/$handler_type", $namespaces, $module_handler, $plugin_interface, $plugin_definition_annotation_name);
$this->setCacheBackend($cache_backend, "views:$handler_type");
$this->alterInfo('views_plugins_' . $handler_type);
$this->viewsData = $views_data;
$this->handlerType = $handler_type;
$this->defaults = array(
'plugin_type' => $handler_type,
);
}
/**
* Fetches a handler from the data cache.
*
* @param array $item
* An associative array representing the handler to be retrieved:
* - table: The name of the table containing the handler.
* - field: The name of the field the handler represents.
* @param string|null $override
* (optional) Override the actual handler object with this plugin ID. Used for
* aggregation when the handler is redirected to the aggregation handler.
*
* @return \Drupal\views\Plugin\views\ViewsHandlerInterface
* An instance of a handler object. May be a broken handler instance.
*/
public function getHandler($item, $override = NULL) {
$table = $item['table'];
$field = $item['field'];
// Get the plugin manager for this type.
$data = $this->viewsData->get($table);
if (isset($data[$field][$this->handlerType])) {
$definition = $data[$field][$this->handlerType];
foreach (array('group', 'title', 'title short', 'label', 'help', 'real field', 'real table', 'entity type', 'entity field') as $key) {
if (!isset($definition[$key])) {
// First check the field level.
if (!empty($data[$field][$key])) {
$definition[$key] = $data[$field][$key];
}
// Then if that doesn't work, check the table level.
elseif (!empty($data['table'][$key])) {
$definition_key = $key === 'entity type' ? 'entity_type' : $key;
$definition[$definition_key] = $data['table'][$key];
}
}
}
// @todo This is crazy. Find a way to remove the override functionality.
$plugin_id = $override ?: $definition['id'];
// Try to use the overridden handler.
$handler = $this->createInstance($plugin_id, $definition);
if ($override && method_exists($handler, 'broken') && $handler->broken()) {
$handler = $this->createInstance($definition['id'], $definition);
}
return $handler;
}
// Finally, use the 'broken' handler.
return $this->createInstance('broken', array('original_configuration' => $item));
}
/**
* {@inheritdoc}
*/
public function createInstance($plugin_id, array $configuration = array()) {
$instance = parent::createInstance($plugin_id, $configuration);
if ($instance instanceof HandlerBase) {
$instance->setModuleHandler($this->moduleHandler);
$instance->setViewsData($this->viewsData);
}
return $instance;
}
/**
* {@inheritdoc}
*/
public function getFallbackPluginId($plugin_id, array $configuration = array()) {
return 'broken';
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Drupal\views\Plugin;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Symfony\Component\DependencyInjection\Container;
/**
* Plugin type manager for all views plugins.
*
* @ingroup views_plugins
*/
class ViewsPluginManager extends DefaultPluginManager {
/**
* Constructs a ViewsPluginManager object.
*
* @param string $type
* The plugin type, for example filter.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations,
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
$plugin_definition_annotation_name = 'Drupal\views\Annotation\Views' . Container::camelize($type);
parent::__construct("Plugin/views/$type", $namespaces, $module_handler, 'Drupal\views\Plugin\views\ViewsPluginInterface', $plugin_definition_annotation_name);
$this->defaults += array(
'parent' => 'parent',
'plugin_type' => $type,
'register_theme' => TRUE,
);
$this->alterInfo('views_plugins_' . $type);
$this->setCacheBackend($cache_backend, "views:$type");
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Drupal\views\Plugin\views;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Form\FormStateInterface;
/**
* A Trait for Views broken handlers.
*/
trait BrokenHandlerTrait {
/**
* Returns this handlers name in the UI.
*
* @see \Drupal\views\Plugin\views\PluginBase::defineOptions()
*/
public function adminLabel($short = FALSE) {
return t('Broken/missing handler');
}
/**
* The option definition for this handler.
*
* @see \Drupal\views\Plugin\views\PluginBase::defineOptions()
*/
public function defineOptions() {
return array();
}
/**
* Ensure the main table for this handler is in the query. This is used
* a lot.
*
* @see \Drupal\views\Plugin\views\HandlerBase::ensureMyTable()
*/
public function ensureMyTable() {
// No table to ensure.
}
/**
* Modify the views query.
*/
public function query($group_by = FALSE) {
/* No query to run */
}
/**
* Provides a form to edit options for this plugin.
*
* @see \Drupal\views\Plugin\views\PluginBase::defineOptions()
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
$description_top = t('The handler for this item is broken or missing. The following details are available:');
foreach ($this->definition['original_configuration'] as $key => $value) {
if (is_scalar($value)) {
$items[] = SafeMarkup::format('@key: @value', array('@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.');
$form['description'] = array(
'#type' => 'container',
'#attributes' => array(
'class' => array('js-form-item', 'form-item', 'description'),
),
'description_top' => array(
'#markup' => '<p>' . $description_top . '</p>',
),
'detail_list' => array(
'#theme' => 'item_list',
'#items' => $items,
),
'description_bottom' => array(
'#markup' => '<p>' . $description_bottom . '</p>',
),
);
}
/**
* Determines if the handler is considered 'broken'.
*
* This means it's a placeholder used when a handler can't be found.
*
* @see \Drupal\views\Plugin\views\HandlerBase::broken()
*/
public function broken() {
return TRUE;
}
/**
* Gets dependencies for a broken handler.
*
* @return array
*
* @see \Drupal\views\Plugin\views\PluginBase::calculateDependencies()
*/
public function calculateDependencies() {
return [];
}
}

View file

@ -0,0 +1,834 @@
<?php
namespace Drupal\views\Plugin\views;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Unicode;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Render\ViewsRenderPipelineMarkup;
use Drupal\views\ViewExecutable;
use Drupal\views\Views;
use Drupal\views\ViewsData;
/**
* Base class for Views handler plugins.
*
* @ingroup views_plugins
*/
abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
/**
* Where the $query object will reside:
*
* @var \Drupal\views\Plugin\views\query\QueryPluginBase
*/
public $query = NULL;
/**
* The table this handler is attached to.
*
* @var string
*/
public $table;
/**
* The alias of the table of this handler which is used in the query.
*
* @var string
*/
public $tableAlias;
/**
* The actual field in the database table, maybe different
* on other kind of query plugins/special handlers.
*
* @var string
*/
public $realField;
/**
* With field you can override the realField if the real field is not set.
*
* @var string
*/
public $field;
/**
* The relationship used for this field.
*
* @var string
*/
public $relationship = NULL;
/**
* The module handler.
*
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* The views data service.
*
* @var \Drupal\views\ViewsData
*/
protected $viewsData;
/**
* Constructs a Handler 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.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->is_handler = TRUE;
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
// Check to see if this handler type is defaulted. Note that
// we have to do a lookup because the type is singular but the
// option is stored as the plural.
$this->unpackOptions($this->options, $options);
// This exist on most handlers, but not all. So they are still optional.
if (isset($options['table'])) {
$this->table = $options['table'];
}
// Allow aliases on both fields and tables.
if (isset($this->definition['real table'])) {
$this->table = $this->definition['real table'];
}
if (isset($this->definition['real field'])) {
$this->realField = $this->definition['real field'];
}
if (isset($this->definition['field'])) {
$this->realField = $this->definition['field'];
}
if (isset($options['field'])) {
$this->field = $options['field'];
if (!isset($this->realField)) {
$this->realField = $options['field'];
}
}
$this->query = &$view->query;
}
protected function defineOptions() {
$options = parent::defineOptions();
$options['id'] = array('default' => '');
$options['table'] = array('default' => '');
$options['field'] = array('default' => '');
$options['relationship'] = array('default' => 'none');
$options['group_type'] = array('default' => 'group');
$options['admin_label'] = array('default' => '');
return $options;
}
/**
* {@inheritdoc}
*/
public function adminLabel($short = FALSE) {
if (!empty($this->options['admin_label'])) {
return $this->options['admin_label'];
}
$title = ($short && isset($this->definition['title short'])) ? $this->definition['title short'] : $this->definition['title'];
return $this->t('@group: @title', array('@group' => $this->definition['group'], '@title' => $title));
}
/**
* {@inheritdoc}
*/
public function getField($field = NULL) {
if (!isset($field)) {
if (!empty($this->formula)) {
$field = $this->getFormula();
}
else {
$field = $this->tableAlias . '.' . $this->realField;
}
}
// If grouping, check to see if the aggregation method needs to modify the field.
if ($this->view->display_handler->useGroupBy()) {
$this->view->initQuery();
if ($this->query) {
$info = $this->query->getAggregationInfo();
if (!empty($info[$this->options['group_type']]['method'])) {
$method = $info[$this->options['group_type']]['method'];
if (method_exists($this->query, $method)) {
return $this->query->$method($this->options['group_type'], $field);
}
}
}
}
return $field;
}
/**
* {@inheritdoc}
*/
public function sanitizeValue($value, $type = NULL) {
switch ($type) {
case 'xss':
$value = Xss::filter($value);
break;
case 'xss_admin':
$value = Xss::filterAdmin($value);
break;
case 'url':
$value = Html::escape(UrlHelper::stripDangerousProtocols($value));
break;
default:
$value = Html::escape($value);
break;
}
return ViewsRenderPipelineMarkup::create($value);
}
/**
* Transform a string by a certain method.
*
* @param $string
* 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.
*
* @return string
* The transformed string.
*/
protected function caseTransform($string, $option) {
switch ($option) {
default:
return $string;
case 'upper':
return Unicode::strtoupper($string);
case 'lower':
return Unicode::strtolower($string);
case 'ucfirst':
return Unicode::ucfirst($string);
case 'ucwords':
return Unicode::ucwords($string);
}
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
// Some form elements belong in a fieldset for presentation, but can't
// be moved into one because of the $form_state->getValues() hierarchy. Those
// elements can add a #fieldset => 'fieldset_name' property, and they'll
// be moved to their fieldset during pre_render.
$form['#pre_render'][] = array(get_class($this), 'preRenderAddFieldsetMarkup');
parent::buildOptionsForm($form, $form_state);
$form['fieldsets'] = array(
'#type' => 'value',
'#value' => array('more', 'admin_label'),
);
$form['admin_label'] = array(
'#type' => 'details',
'#title' => $this->t('Administrative title'),
'#weight' => 150,
);
$form['admin_label']['admin_label'] = array(
'#type' => 'textfield',
'#title' => $this->t('Administrative title'),
'#description' => $this->t('This title will be displayed on the views edit page instead of the default one. This might be useful if you have the same item twice.'),
'#default_value' => $this->options['admin_label'],
'#parents' => array('options', 'admin_label'),
);
// This form is long and messy enough that the "Administrative title" option
// belongs in "Administrative title" fieldset at the bottom of the form.
$form['more'] = array(
'#type' => 'details',
'#title' => $this->t('More'),
'#weight' => 200,
'#optional' => TRUE,
);
// Allow to alter the default values brought into the form.
// @todo Do we really want to keep this hook.
$this->getModuleHandler()->alter('views_handler_options', $this->options, $this->view);
}
/**
* Gets the module handler.
*
* @return \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected function getModuleHandler() {
if (!$this->moduleHandler) {
$this->moduleHandler = \Drupal::moduleHandler();
}
return $this->moduleHandler;
}
/**
* Sets the module handler.
*
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function setModuleHandler(ModuleHandlerInterface $module_handler) {
$this->moduleHandler = $module_handler;
}
/**
* Provides the handler some groupby.
*/
public function usesGroupBy() {
return TRUE;
}
/**
* Provide a form for aggregation settings.
*/
public function buildGroupByForm(&$form, FormStateInterface $form_state) {
$display_id = $form_state->get('display_id');
$type = $form_state->get('type');
$id = $form_state->get('id');
$form['#section'] = $display_id . '-' . $type . '-' . $id;
$this->view->initQuery();
$info = $this->view->query->getAggregationInfo();
foreach ($info as $id => $aggregate) {
$group_types[$id] = $aggregate['title'];
}
$form['group_type'] = array(
'#type' => 'select',
'#title' => $this->t('Aggregation type'),
'#default_value' => $this->options['group_type'],
'#description' => $this->t('Select the aggregation function to use on this field.'),
'#options' => $group_types,
);
}
/**
* 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 submitGroupByForm(&$form, FormStateInterface $form_state) {
$form_state->get('handler')->options['group_type'] = $form_state->getValue(['options', 'group_type']);
}
/**
* If a handler has 'extra options' it will get a little settings widget and
* another form called extra_options.
*/
public function hasExtraOptions() { return FALSE; }
/**
* Provide defaults for the handler.
*/
public function defineExtraOptions(&$option) { }
/**
* Provide a form for setting options.
*/
public function buildExtraOptionsForm(&$form, FormStateInterface $form_state) { }
/**
* Validate the options form.
*/
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) { }
/**
* Determine if a handler can be exposed.
*/
public function canExpose() { return FALSE; }
/**
* Set new exposed option defaults when exposed setting is flipped
* on.
*/
public function defaultExposeOptions() { }
/**
* Get information about the exposed form for the form renderer.
*/
public function exposedInfo() { }
/**
* Render our chunk of the exposed handler form when selecting
*/
public function buildExposedForm(&$form, FormStateInterface $form_state) { }
/**
* Validate the exposed handler form
*/
public function validateExposed(&$form, FormStateInterface $form_state) { }
/**
* Submit the exposed handler form
*/
public function submitExposed(&$form, FormStateInterface $form_state) { }
/**
* Form for exposed handler options.
*/
public function buildExposeForm(&$form, FormStateInterface $form_state) { }
/**
* Validate the options form.
*/
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) { }
/**
* Shortcut to display the expose/hide button.
*/
public function showExposeButton(&$form, FormStateInterface $form_state) { }
/**
* Shortcut to display the exposed options form.
*/
public function showExposeForm(&$form, FormStateInterface $form_state) {
if (empty($this->options['exposed'])) {
return;
}
$this->buildExposeForm($form, $form_state);
// When we click the expose button, we add new gadgets to the form but they
// have no data in POST so their defaults get wiped out. This prevents
// these defaults from getting wiped out. This setting will only be TRUE
// during a 2nd pass rerender.
if ($form_state->get('force_expose_options')) {
foreach (Element::children($form['expose']) as $id) {
if (isset($form['expose'][$id]['#default_value']) && !isset($form['expose'][$id]['#value'])) {
$form['expose'][$id]['#value'] = $form['expose'][$id]['#default_value'];
}
}
}
}
/**
* {@inheritdoc}
*/
public function access(AccountInterface $account) {
if (isset($this->definition['access callback']) && function_exists($this->definition['access callback'])) {
if (isset($this->definition['access arguments']) && is_array($this->definition['access arguments'])) {
return call_user_func_array($this->definition['access callback'], array($account) + $this->definition['access arguments']);
}
return $this->definition['access callback']($account);
}
return TRUE;
}
/**
* {@inheritdoc}
*/
public function preQuery() {
}
/**
* {@inheritdoc}
*/
public function query() {
}
/**
* {@inheritdoc}
*/
public function postExecute(&$values) { }
/**
* Provides a unique placeholders for handlers.
*
* @return string
* A placeholder which contains the table and the fieldname.
*/
protected function placeholder() {
return $this->query->placeholder($this->table . '_' . $this->field);
}
/**
* {@inheritdoc}
*/
public function setRelationship() {
// Ensure this gets set to something.
$this->relationship = NULL;
// Don't process non-existent relationships.
if (empty($this->options['relationship']) || $this->options['relationship'] == 'none') {
return;
}
$relationship = $this->options['relationship'];
// Ignore missing/broken relationships.
if (empty($this->view->relationship[$relationship])) {
return;
}
// Check to see if the relationship has already processed. If not, then we
// cannot process it.
if (empty($this->view->relationship[$relationship]->alias)) {
return;
}
// Finally!
$this->relationship = $this->view->relationship[$relationship]->alias;
}
/**
* {@inheritdoc}
*/
public function ensureMyTable() {
if (!isset($this->tableAlias)) {
$this->tableAlias = $this->query->ensureTable($this->table, $this->relationship);
}
return $this->tableAlias;
}
/**
* {@inheritdoc}
*/
public function adminSummary() { }
/**
* Determine if this item is 'exposed', meaning it provides form elements
* to let users modify the view.
*
* @return TRUE/FALSE
*/
public function isExposed() {
return !empty($this->options['exposed']);
}
/**
* Returns TRUE if the exposed filter works like a grouped filter.
*/
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; }
/**
* Take input from exposed handlers and assign to this handler, if necessary.
*/
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; }
/**
* {@inheritdoc}
*/
public function getJoin() {
// get the join from this table that links back to the base table.
// Determine the primary table to seek
if (empty($this->query->relationships[$this->relationship])) {
$base_table = $this->view->storage->get('base_table');
}
else {
$base_table = $this->query->relationships[$this->relationship]['base'];
}
$join = $this->getTableJoin($this->table, $base_table);
if ($join) {
return clone $join;
}
}
/**
* {@inheritdoc}
*/
public function validate() { return array(); }
/**
* {@inheritdoc}
*/
public function broken() {
return FALSE;
}
/**
* Creates cross-database SQL date formatting.
*
* @param string $format
* A format string for the result, like 'Y-m-d H:i:s'.
*
* @return string
* An appropriate SQL string for the DB type and field type.
*/
public function getDateFormat($format) {
return $this->query->getDateFormat($this->getDateField(), $format);
}
/**
* Creates cross-database SQL dates.
*
* @return string
* An appropriate SQL string for the db type and field type.
*/
public function getDateField() {
return $this->query->getDateField("$this->tableAlias.$this->realField");
}
/**
* Gets views data service.
*
* @return \Drupal\views\ViewsData
*/
protected function getViewsData() {
if (!$this->viewsData) {
$this->viewsData = Views::viewsData();
}
return $this->viewsData;
}
/**
* {@inheritdoc}
*/
public function setViewsData(ViewsData $views_data) {
$this->viewsData = $views_data;
}
/**
* {@inheritdoc}
*/
public static function getTableJoin($table, $base_table) {
$data = Views::viewsData()->get($table);
if (isset($data['table']['join'][$base_table])) {
$join_info = $data['table']['join'][$base_table];
if (!empty($join_info['join_id'])) {
$id = $join_info['join_id'];
}
else {
$id = 'standard';
}
$configuration = $join_info;
// Fill in some easy defaults.
if (empty($configuration['table'])) {
$configuration['table'] = $table;
}
// If this is empty, it's a direct link.
if (empty($configuration['left_table'])) {
$configuration['left_table'] = $base_table;
}
if (isset($join_info['arguments'])) {
foreach ($join_info['arguments'] as $key => $argument) {
$configuration[$key] = $argument;
}
}
$join = Views::pluginManager('join')->createInstance($id, $configuration);
return $join;
}
}
/**
* {@inheritdoc}
*/
public function getEntityType() {
// If the user has configured a relationship on the handler take that into
// account.
if (!empty($this->options['relationship']) && $this->options['relationship'] != 'none') {
$relationship = $this->displayHandler->getOption('relationships')[$this->options['relationship']];
$table_data = $this->getViewsData()->get($relationship['table']);
$views_data = $this->getViewsData()->get($table_data[$relationship['field']]['relationship']['base']);
}
else {
$views_data = $this->getViewsData()->get($this->view->storage->get('base_table'));
}
if (isset($views_data['table']['entity type'])) {
return $views_data['table']['entity type'];
}
else {
throw new \Exception("No entity type for field {$this->options['id']} on view {$this->view->storage->id()}");
}
}
/**
* {@inheritdoc}
*/
public static function breakString($str, $force_int = FALSE) {
$operator = NULL;
$value = array();
// Determine if the string has 'or' operators (plus signs) or 'and'
// operators (commas) and split the string accordingly.
if (preg_match('/^([\w0-9-_\.]+[+ ]+)+[\w0-9-_\.]+$/u', $str)) {
// The '+' character in a query string may be parsed as ' '.
$operator = 'or';
$value = preg_split('/[+ ]/', $str);
}
elseif (preg_match('/^([\w0-9-_\.]+[, ]+)*[\w0-9-_\.]+$/u', $str)) {
$operator = 'and';
$value = explode(',', $str);
}
// Filter any empty matches (Like from '++' in a string) and reset the
// array keys. 'strlen' is used as the filter callback so we do not lose
// 0 values (would otherwise evaluate == FALSE).
$value = array_values(array_filter($value, 'strlen'));
if ($force_int) {
$value = array_map('intval', $value);
}
return (object) array('value' => $value, 'operator' => $operator);
}
/**
* Displays the Expose form.
*/
public function displayExposedForm($form, FormStateInterface $form_state) {
$item = &$this->options;
// flip
$item['exposed'] = empty($item['exposed']);
// If necessary, set new defaults:
if ($item['exposed']) {
$this->defaultExposeOptions();
}
$view = $form_state->get('view');
$display_id = $form_state->get('display_id');
$type = $form_state->get('type');
$id = $form_state->get('id');
$view->getExecutable()->setHandler($display_id, $type, $id, $item);
$view->addFormToStack($form_state->get('form_key'), $display_id, $type, $id, TRUE, TRUE);
$view->cacheSet();
$form_state->set('rerender', TRUE);
$form_state->setRebuild();
$form_state->set('force_expose_options', TRUE);
}
/**
* A submit handler that is used for storing temporary items when using
* multi-step changes, such as ajax requests.
*/
public function submitTemporaryForm($form, FormStateInterface $form_state) {
// Run it through the handler's submit function.
$this->submitOptionsForm($form['options'], $form_state);
$item = $this->options;
$types = ViewExecutable::getHandlerTypes();
// For footer/header $handler_type is area but $type is footer/header.
// For all other handle types it's the same.
$handler_type = $type = $form_state->get('type');
if (!empty($types[$type]['type'])) {
$handler_type = $types[$type]['type'];
}
$override = NULL;
$view = $form_state->get('view');
$executable = $view->getExecutable();
if ($executable->display_handler->useGroupBy() && !empty($item['group_type'])) {
if (empty($executable->query)) {
$executable->initQuery();
}
$aggregate = $executable->query->getAggregationInfo();
if (!empty($aggregate[$item['group_type']]['handler'][$type])) {
$override = $aggregate[$item['group_type']]['handler'][$type];
}
}
// Create a new handler and unpack the options from the form onto it. We
// can use that for storage.
$handler = Views::handlerManager($handler_type)->getHandler($item, $override);
$handler->init($executable, $executable->display_handler, $item);
// Add the incoming options to existing options because items using
// the extra form may not have everything in the form here.
$options = $form_state->getValue('options') + $this->options;
// This unpacks only options that are in the definition, ensuring random
// extra stuff on the form is not sent through.
$handler->unpackOptions($handler->options, $options, NULL, FALSE);
// Store the item back on the view.
$executable = $view->getExecutable();
$executable->temporary_options[$type][$form_state->get('id')] = $handler->options;
// @todo Decide if \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm() is
// perhaps the better place to fix the issue.
// \Drupal\views_ui\Form\Ajax\ViewsFormBase::getForm() drops the current
// form from the stack, even if it's an #ajax. So add the item back to the top
// of the stack.
$view->addFormToStack($form_state->get('form_key'), $form_state->get('display_id'), $type, $item['id'], TRUE);
$form_state->get('rerender', TRUE);
$form_state->setRebuild();
// Write to cache
$view->cacheSet();
}
/**
* Calculates options stored on the handler
*
* @param array $options
* The options stored in the handler
* @param array $form_state_options
* The newly submitted form state options.
*
* @return array
* The new options
*/
public function submitFormCalculateOptions(array $options, array $form_state_options) {
return $form_state_options + $options;
}
}

View file

@ -0,0 +1,653 @@
<?php
namespace Drupal\views\Plugin\views;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Plugin\PluginBase as ComponentPluginBase;
use Drupal\Core\Render\Element;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base class for any views plugin types.
*
* Via the @Plugin definition the plugin may specify a theme function or
* template to be used for the plugin. It also can auto-register the theme
* implementation for that file or function.
* - theme: the theme implementation to use in the plugin. This may be the name
* of the function (without theme_ prefix) or the template file (without
* template engine extension).
* If a template file should be used, the file has to be placed in the
* module's templates folder.
* Example: theme = "mymodule_row" of module "mymodule" will implement
* mymodule-row.html.twig in the [..]/modules/mymodule/templates folder.
* - register_theme: (optional) When set to TRUE (default) the theme is
* registered automatically. When set to FALSE the plugin reuses an existing
* theme implementation, defined by another module or views plugin.
* - theme_file: (optional) the location of an include file that may hold the
* theme or preprocess function. The location has to be relative to module's
* root directory.
* - module: machine name of the module. It must be present for any plugin that
* wants to register a theme.
*
* @ingroup views_plugins
*/
abstract class PluginBase extends ComponentPluginBase implements ContainerFactoryPluginInterface, ViewsPluginInterface, DependentPluginInterface {
/**
* Include negotiated languages when listing languages.
*
* @see \Drupal\views\Plugin\views\PluginBase::listLanguages()
*/
const INCLUDE_NEGOTIATED = 16;
/**
* Include entity row languages when listing languages.
*
* @see \Drupal\views\Plugin\views\PluginBase::listLanguages()
*/
const INCLUDE_ENTITY = 32;
/**
* Query string to indicate the site default language.
*
* @see \Drupal\Core\Language\LanguageInterface::LANGCODE_DEFAULT
*/
const VIEWS_QUERY_LANGUAGE_SITE_DEFAULT = '***LANGUAGE_site_default***';
/**
* Options for this plugin will be held here.
*
* @var array
*/
public $options = array();
/**
* The top object of a view.
*
* @var \Drupal\views\ViewExecutable
*/
public $view = NULL;
/**
* The display object this plugin is for.
*
* For display plugins this is empty.
*
* @todo find a better description
*
* @var \Drupal\views\Plugin\views\display\DisplayPluginBase
*/
public $displayHandler;
/**
* Plugins's definition
*
* @var array
*/
public $definition;
/**
* Denotes whether the plugin has an additional options form.
*
* @var bool
*/
protected $usesOptions = FALSE;
/**
* Stores the render API renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a PluginBase object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->definition = $plugin_definition + $configuration;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static($configuration, $plugin_id, $plugin_definition);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
$this->view = $view;
$this->setOptionDefaults($this->options, $this->defineOptions());
$this->displayHandler = $display;
$this->unpackOptions($this->options, $options);
}
/**
* Information about options for all kinds of purposes will be held here.
* @code
* 'option_name' => array(
* - 'default' => default value,
* - 'contains' => (optional) array of items this contains, with its own
* defaults, etc. If contains is set, the default will be ignored and
* assumed to be array().
* ),
* @endcode
*
* @return array
* Returns the options of this handler/plugin.
*/
protected function defineOptions() { return array(); }
/**
* Fills up the options of the plugin with defaults.
*
* @param array $storage
* An array which stores the actual option values of the plugin.
* @param array $options
* An array which describes the options of a plugin. Each element is an
* associative array containing:
* - default: The default value of one option. Should be translated to the
* interface text language selected for page if translatable.
* - (optional) contains: An array which describes the available options
* under the key. If contains is set, the default will be ignored and
* assumed to be an empty array.
* - (optional) 'bool': TRUE if the value is boolean, else FALSE.
*/
protected function setOptionDefaults(array &$storage, array $options) {
foreach ($options as $option => $definition) {
if (isset($definition['contains'])) {
$storage[$option] = array();
$this->setOptionDefaults($storage[$option], $definition['contains']);
}
else {
$storage[$option] = $definition['default'];
}
}
}
/**
* {@inheritdoc}
*/
public function filterByDefinedOptions(array &$storage) {
$this->doFilterByDefinedOptions($storage, $this->defineOptions());
}
/**
* Do the work to filter out stored options depending on the defined options.
*
* @param array $storage
* The stored options.
* @param array $options
* The defined options.
*/
protected function doFilterByDefinedOptions(array &$storage, array $options) {
foreach ($storage as $key => $sub_storage) {
if (!isset($options[$key])) {
unset($storage[$key]);
}
if (isset($options[$key]['contains'])) {
$this->doFilterByDefinedOptions($storage[$key], $options[$key]['contains']);
}
}
}
/**
* {@inheritdoc}
*/
public function unpackOptions(&$storage, $options, $definition = NULL, $all = TRUE, $check = TRUE) {
if ($check && !is_array($options)) {
return;
}
if (!isset($definition)) {
$definition = $this->defineOptions();
}
foreach ($options as $key => $value) {
if (is_array($value)) {
// Ignore arrays with no definition.
if (!$all && empty($definition[$key])) {
continue;
}
if (!isset($storage[$key]) || !is_array($storage[$key])) {
$storage[$key] = array();
}
// If we're just unpacking our known options, and we're dropping an
// unknown array (as might happen for a dependent plugin fields) go
// ahead and drop that in.
if (!$all && isset($definition[$key]) && !isset($definition[$key]['contains'])) {
$storage[$key] = $value;
continue;
}
$this->unpackOptions($storage[$key], $value, isset($definition[$key]['contains']) ? $definition[$key]['contains'] : array(), $all, FALSE);
}
elseif ($all || !empty($definition[$key])) {
$storage[$key] = $value;
}
}
}
/**
* {@inheritdoc}
*/
public function destroy() {
unset($this->view, $this->display, $this->query);
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
// Some form elements belong in a fieldset for presentation, but can't
// be moved into one because of the $form_state->getValues() hierarchy. Those
// elements can add a #fieldset => 'fieldset_name' property, and they'll
// be moved to their fieldset during pre_render.
$form['#pre_render'][] = array(get_class($this), 'preRenderAddFieldsetMarkup');
}
/**
* {@inheritdoc}
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state) { }
/**
* {@inheritdoc}
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) { }
/**
* {@inheritdoc}
*/
public function query() { }
/**
* {@inheritdoc}
*/
public function themeFunctions() {
return $this->view->buildThemeFunctions($this->definition['theme']);
}
/**
* {@inheritdoc}
*/
public function validate() { return array(); }
/**
* {@inheritdoc}
*/
public function summaryTitle() {
return $this->t('Settings');
}
/**
* {@inheritdoc}
*/
public function pluginTitle() {
// Short_title is optional so its defaults to an empty string.
if (!empty($this->definition['short_title'])) {
return $this->definition['short_title'];
}
return $this->definition['title'];
}
/**
* {@inheritdoc}
*/
public function usesOptions() {
return $this->usesOptions;
}
/**
* {@inheritdoc}
*/
public function globalTokenReplace($string = '', array $options = array()) {
return \Drupal::token()->replace($string, array('view' => $this->view), $options);
}
/**
* Replaces Views' tokens in a given string. The resulting string will be
* sanitized with Xss::filterAdmin.
*
* @param $text
* Unsanitized string with possible tokens.
* @param $tokens
* Array of token => replacement_value items.
*
* @return string
*/
protected function viewsTokenReplace($text, $tokens) {
if (!strlen($text)) {
// No need to run filterAdmin on an empty string.
return '';
}
if (empty($tokens)) {
return Xss::filterAdmin($text);
}
$twig_tokens = array();
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
if (strpos($token, '{{') !== FALSE) {
// Twig wants a token replacement array stripped of curly-brackets.
$token = trim(str_replace(['{{', '}}'], '', $token));
}
// Check for arrays in Twig tokens. Internally these are passed as
// dot-delimited strings, but need to be turned into associative arrays
// for parsing.
if (strpos($token, '.') === FALSE) {
// 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.');
$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.');
$token_array = 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.');
$token_array = array($key => $token_array);
}
if (!isset($twig_tokens[$top])) {
$twig_tokens[$top] = [];
}
$twig_tokens[$top] += $token_array;
}
}
if ($twig_tokens) {
// Use the unfiltered text for the Twig template, then filter the output.
// Otherwise, Xss::filterAdmin could remove valid Twig syntax before the
// template is parsed.
$build = array(
'#type' => 'inline_template',
'#template' => $text,
'#context' => $twig_tokens,
'#post_render' => [
function ($children, $elements) {
return Xss::filterAdmin($children);
}
],
);
// Currently you cannot attach assets to tokens with
// Renderer::renderPlain(). This may be unnecessarily limiting. Consider
// using Renderer::executeInRenderContext() instead.
// @todo: https://www.drupal.org/node/2566621
return (string) $this->getRenderer()->renderPlain($build);
}
else {
return Xss::filterAdmin($text);
}
}
/**
* {@inheritdoc}
*/
public function getAvailableGlobalTokens($prepared = FALSE, array $types = array()) {
$info = \Drupal::token()->getInfo();
// Site and view tokens should always be available.
$types += array('site', 'view');
$available = array_intersect_key($info['tokens'], array_flip($types));
// Construct the token string for each token.
if ($prepared) {
$prepared = array();
foreach ($available as $type => $tokens) {
foreach (array_keys($tokens) as $token) {
$prepared[$type][] = "[$type:$token]";
}
}
return $prepared;
}
return $available;
}
/**
* {@inheritdoc}
*/
public function globalTokenForm(&$form, FormStateInterface $form_state) {
$token_items = array();
foreach ($this->getAvailableGlobalTokens() as $type => $tokens) {
$item = array(
'#markup' => $type,
'children' => array(),
);
foreach ($tokens as $name => $info) {
$item['children'][$name] = "[$type:$name]" . ' - ' . $info['name'] . ': ' . $info['description'];
}
$token_items[$type] = $item;
}
$form['global_tokens'] = array(
'#type' => 'details',
'#title' => $this->t('Available global token replacements'),
);
$form['global_tokens']['list'] = array(
'#theme' => 'item_list',
'#items' => $token_items,
'#attributes' => array(
'class' => array('global-tokens'),
),
);
}
/**
* {@inheritdoc}
*/
public static function preRenderAddFieldsetMarkup(array $form) {
foreach (Element::children($form) as $key) {
$element = $form[$key];
// In our form builder functions, we added an arbitrary #fieldset property
// to any element that belongs in a fieldset. If this form element has
// that property, move it into its fieldset.
if (isset($element['#fieldset']) && isset($form[$element['#fieldset']])) {
$form[$element['#fieldset']][$key] = $element;
// Remove the original element this duplicates.
unset($form[$key]);
}
}
return $form;
}
/**
* {@inheritdoc}
*/
public static function preRenderFlattenData($form) {
foreach (Element::children($form) as $key) {
$element = $form[$key];
if (!empty($element['#flatten'])) {
foreach (Element::children($element) as $child_key) {
$form[$child_key] = $form[$key][$child_key];
}
// All done, remove the now-empty parent.
unset($form[$key]);
}
}
return $form;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
/**
* {@inheritdoc}
*/
public function getProvider() {
$definition = $this->getPluginDefinition();
return $definition['provider'];
}
/**
* Makes an array of languages, optionally including special languages.
*
* @param int $flags
* (optional) Flags for which languages to return (additive). Options:
* - \Drupal\Core\Language::STATE_ALL (default): All languages
* (configurable and default).
* - \Drupal\Core\Language::STATE_CONFIGURABLE: Configurable languages.
* - \Drupal\Core\Language::STATE_LOCKED: Locked languages.
* - \Drupal\Core\Language::STATE_SITE_DEFAULT: Add site default language;
* note that this is not included in STATE_ALL.
* - \Drupal\views\Plugin\views\PluginBase::INCLUDE_NEGOTIATED: Add
* negotiated language types.
* - \Drupal\views\Plugin\views\PluginBase::INCLUDE_ENTITY: Add
* entity row language types. Note that these are only supported for
* display options, not substituted in queries.
* @param array|null $current_values
* The currently-selected options in the list, if available.
*
* @return array
* An array of language names, keyed by the language code. Negotiated and
* special languages have special codes that are substituted in queries by
* PluginBase::queryLanguageSubstitutions().
* Only configurable languages and languages that are in $current_values are
* included in the list.
*/
protected function listLanguages($flags = LanguageInterface::STATE_ALL, array $current_values = NULL) {
$manager = \Drupal::languageManager();
$languages = $manager->getLanguages($flags);
$list = array();
// The entity languages should come first, if requested.
if ($flags & PluginBase::INCLUDE_ENTITY) {
$list['***LANGUAGE_entity_translation***'] = $this->t('Content language of view row');
$list['***LANGUAGE_entity_default***'] = $this->t('Original language of content in view row');
}
// STATE_SITE_DEFAULT comes in with ID set
// to LanguageInterface::LANGCODE_SITE_DEFAULT.
// Since this is not a real language, surround it by '***LANGUAGE_...***',
// like the negotiated languages below.
if ($flags & LanguageInterface::STATE_SITE_DEFAULT) {
$name = $languages[LanguageInterface::LANGCODE_SITE_DEFAULT]->getName();
// The language name may have already been translated, no need to
// translate it again.
// @see Drupal\Core\Language::filterLanguages().
if (!$name instanceof TranslatableMarkup) {
$name = $this->t($name);
}
$list[PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT] = $name;
// Remove site default language from $languages so it's not added
// twice with the real languages below.
unset($languages[LanguageInterface::LANGCODE_SITE_DEFAULT]);
}
// Add in negotiated languages, if requested.
if ($flags & PluginBase::INCLUDE_NEGOTIATED) {
$types_info = $manager->getDefinedLanguageTypesInfo();
$types = $manager->getLanguageTypes();
// We only go through the configured types.
foreach ($types as $id) {
if (isset($types_info[$id]['name'])) {
$name = $types_info[$id]['name'];
// Surround IDs by '***LANGUAGE_...***', to avoid query collisions.
$id = '***LANGUAGE_' . $id . '***';
$list[$id] = $this->t('@type language selected for page', array('@type' => $name));
}
}
if (!empty($current_values)) {
foreach ($types_info as $id => $type) {
$id = '***LANGUAGE_' . $id . '***';
// If this (non-configurable) type is among the current values,
// add that option too, so it is not lost. If not among the current
// values, skip displaying it to avoid user confusion.
if (isset($type['name']) && !isset($list[$id]) && in_array($id, $current_values)) {
$list[$id] = $this->t('@type language selected for page', array('@type' => $type['name']));
}
}
}
}
// Add real languages.
foreach ($languages as $id => $language) {
$list[$id] = $language->getName();
}
return $list;
}
/**
* Returns substitutions for Views queries for languages.
*
* This is needed so that the language options returned by
* PluginBase::listLanguages() are able to be used in queries. It is called
* by the Views module implementation of hook_views_query_substitutions()
* to get the language-related substitutions.
*
* @return array
* An array in the format of hook_views_query_substitutions() that gives
* the query substitutions needed for the special language types.
*/
public static function queryLanguageSubstitutions() {
$changes = array();
$manager = \Drupal::languageManager();
// Handle default language.
$default = $manager->getDefaultLanguage()->getId();
$changes[PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT] = $default;
// Handle negotiated languages.
$types = $manager->getDefinedLanguageTypesInfo();
foreach ($types as $id => $type) {
if (isset($type['name'])) {
$changes['***LANGUAGE_' . $id . '***'] = $manager->getCurrentLanguage($id)->getId();
}
}
return $changes;
}
/**
* Returns the render API renderer.
*
* @return \Drupal\Core\Render\RendererInterface
*/
protected function getRenderer() {
if (!isset($this->renderer)) {
$this->renderer = \Drupal::service('renderer');
}
return $this->renderer;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Drupal\views\Plugin\views;
use Drupal\Component\Plugin\PluginInspectionInterface;
interface PluginInterface extends PluginInspectionInterface {
}

View file

@ -0,0 +1,142 @@
<?php
namespace Drupal\views\Plugin\views;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Provides an interface for all views handlers.
*/
interface ViewsHandlerInterface extends ViewsPluginInterface {
/**
* Run before the view is built.
*
* This gives all the handlers some time to set up before any handler has
* been fully run.
*/
public function preQuery();
/**
* Determines the entity type used by this handler.
*
* If this handler uses a relationship, the base class of the relationship is
* taken into account.
*
* @return string
* The machine name of the entity type.
*/
public function getEntityType();
/**
* Determines if the handler is considered 'broken', meaning it's a
* a placeholder used when a handler can't be found.
*/
public function broken();
/**
* Ensure the main table for this handler is in the query. This is used
* a lot.
*/
public function ensureMyTable();
/**
* Check whether given user has access to this handler.
*
* @param AccountInterface $account
* The user account to check.
*
* @return bool
* TRUE if the user has access to the handler, FALSE otherwise.
*/
public function access(AccountInterface $account);
/**
* Get the join object that should be used for this handler.
*
* This method isn't used a great deal, but it's very handy for easily
* getting the join if it is necessary to make some changes to it, such
* as adding an 'extra'.
*/
public function getJoin();
/**
* Sanitize the value for output.
*
* @param $value
* The value being rendered.
* @param $type
* The type of sanitization needed. If not provided,
* \Drupal\Component\Utility\Html::escape() is used.
*
* @return \Drupal\views\Render\ViewsRenderPipelineMarkup
* Returns the safe value.
*/
public function sanitizeValue($value, $type = NULL);
/**
* Fetches a handler to join one table to a primary table from the data cache.
*
* @param string $table
* The table to join from.
* @param string $base_table
* The table to join to.
*
* @return \Drupal\views\Plugin\views\join\JoinPluginBase
*/
public static function getTableJoin($table, $base_table);
/**
* Shortcut to get a handler's raw field value.
*
* This should be overridden for handlers with formulae or other
* non-standard fields. Because this takes an argument, fields
* overriding this can just call return parent::getField($formula)
*/
public function getField($field = NULL);
/**
* Run after the view is executed, before the result is cached.
*
* This gives all the handlers some time to modify values. This is primarily
* used so that handlers that pull up secondary data can put it in the
* $values so that the raw data can be used externally.
*/
public function postExecute(&$values);
/**
* Shortcut to display the exposed options form.
*/
public function showExposeForm(&$form, FormStateInterface $form_state);
/**
* Called just prior to query(), this lets a handler set up any relationship
* it needs.
*/
public function setRelationship();
/**
* Return a string representing this handler's name in the UI.
*/
public function adminLabel($short = FALSE);
/**
* Breaks x,y,z and x+y+z into an array.
*
* @param string $str
* The string to split.
* @param bool $force_int
* Enforce a numeric check.
*
* @return \stdClass
* A stdClass object containing value and operator properties.
*/
public static function breakString($str, $force_int = FALSE);
/**
* Provide text for the administrative summary.
*/
public function adminSummary();
}

View file

@ -0,0 +1,181 @@
<?php
namespace Drupal\views\Plugin\views;
use Drupal\Component\Plugin\DerivativeInspectionInterface;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an interface for all views plugins.
*/
interface ViewsPluginInterface extends PluginInspectionInterface, DerivativeInspectionInterface {
/**
* Returns the plugin provider.
*
* @return string
*/
public function getProvider();
/**
* Return the human readable name of the display.
*
* This appears on the ui beside each plugin and beside the settings link.
*/
public function pluginTitle();
/**
* Returns the usesOptions property.
*/
public function usesOptions();
/**
* Filter out stored options depending on the defined options.
*
* @param array $storage
* The stored options.
*/
public function filterByDefinedOptions(array &$storage);
/**
* Validate the options form.
*/
public function validateOptionsForm(&$form, FormStateInterface $form_state);
/**
* Returns the summary of the settings in the display.
*/
public function summaryTitle();
/**
* Moves form elements into fieldsets for presentation purposes.
*
* Many views forms use #tree = TRUE to keep their values in a hierarchy for
* easier storage. Moving the form elements into fieldsets during form
* building would break up that hierarchy. Therefore, we wait until the
* pre_render stage, where any changes we make affect presentation only and
* aren't reflected in $form_state->getValues().
*
* @param array $form
* The form build array to alter.
*
* @return array
* The form build array.
*/
public static function preRenderAddFieldsetMarkup(array $form);
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition);
/**
* Initialize the plugin.
*
* @param \Drupal\views\ViewExecutable $view
* The view object.
* @param \Drupal\views\Plugin\views\display\DisplayPluginBase $display
* The display handler.
* @param array $options
* The options configured for this plugin.
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL);
/**
* Handle any special handling on the validate form.
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state);
/**
* Adds elements for available core tokens to a form.
*
* @param array $form
* The form array to alter, passed by reference.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
public function globalTokenForm(&$form, FormStateInterface $form_state);
/**
* Returns an array of available token replacements.
*
* @param bool $prepared
* Whether to return the raw token info for each token or an array of
* prepared tokens for each type. E.g. "[view:name]".
* @param array $types
* An array of additional token types to return, defaults to 'site' and
* 'view'.
*
* @return array
* An array of available token replacement info or tokens, grouped by type.
*/
public function getAvailableGlobalTokens($prepared = FALSE, array $types = array());
/**
* Flattens the structure of form elements.
*
* If a form element has #flatten = TRUE, then all of it's 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.
*
* @param array $form
* The form build array to alter.
*
* @return array
* The form build array.
*/
public static function preRenderFlattenData($form);
/**
* Returns a string with any core tokens replaced.
*
* @param string $string
* The string to preform the token replacement on.
* @param array $options
* An array of options, as passed to \Drupal\Core\Utility\Token::replace().
*
* @return string
* The tokenized string.
*/
public function globalTokenReplace($string = '', array $options = array());
/**
* Clears a plugin.
*/
public function destroy();
/**
* Validate that the plugin is correct and can be saved.
*
* @return
* An array of error strings to tell the user what is wrong with this
* plugin.
*/
public function validate();
/**
* Add anything to the query that we might need to.
*/
public function query();
/**
* Unpack options over our existing defaults, drilling down into arrays
* so that defaults don't get totally blown away.
*/
public function unpackOptions(&$storage, $options, $definition = NULL, $all = TRUE, $check = TRUE);
/**
* Provide a form to edit options for this plugin.
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state);
/**
* Provide a full list of possible theme templates used by this style.
*/
public function themeFunctions();
}

View file

@ -0,0 +1,81 @@
<?php
namespace Drupal\views\Plugin\views\access;
use Drupal\Core\Session\AccountInterface;
use Drupal\views\Plugin\views\PluginBase;
use Symfony\Component\Routing\Route;
/**
* @defgroup views_access_plugins Views access plugins
* @{
* Plugins to handle access checking for views.
*
* Access plugins are responsible for controlling access to the view.
*
* Access plugins extend \Drupal\views\Plugin\views\access\AccessPluginBase,
* implementing the access() and alterRouteDefinition() methods. They must be
* annotated with \Drupal\views\Annotation\ViewsAccess annotation, and they
* must be in namespace directory Plugin\views\access.
*
* @ingroup views_plugins
* @see plugin_api
*/
/**
* The base plugin to handle access control.
*
* Access plugins are responsible for controlling a user's access to the view.
* Views includes plugins for checking user roles and individual permissions.
*
* To define an access control plugin, extend this base class. Your access
* plugin should have an annotation that includes the plugin's metadata, for
* example:
* @Plugin(
* id = "denyall",
* title = @Translation("No Access"),
* help = @Translation("Will not be accessible.")
* )
* The definition should include the following keys:
* - id: The unique identifier of your access plugin.
* - title: The human-readable name for your access plugin.
* - help: A short help message for your plugin.
*
* @see \Drupal\views\Plugin\ViewsPluginManager
*/
abstract class AccessPluginBase extends PluginBase {
/**
* {@inheritdoc}
*/
public function summaryTitle() {
return $this->t('Unknown');
}
/**
* Determine if the current user has access or not.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user who wants to access this view.
*
* @return bool
* Returns whether the user has access to the view.
*/
abstract public function access(AccountInterface $account);
/**
* Allows access plugins to alter the route definition of a view.
*
* Likely the access plugin will add new requirements, so its custom access
* checker can be applied.
*
* @param \Symfony\Component\Routing\Route $route
* The route to change.
*/
abstract public function alterRouteDefinition(Route $route);
}
/**
* @}
*/

View file

@ -0,0 +1,43 @@
<?php
namespace Drupal\views\Plugin\views\access;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\Routing\Route;
/**
* Access plugin that provides no access control at all.
*
* @ingroup views_access_plugins
*
* @ViewsAccess(
* id = "none",
* title = @Translation("None"),
* help = @Translation("Will be available to all users.")
* )
*/
class None extends AccessPluginBase {
/**
* {@inheritdoc}
*/
public function summaryTitle() {
return $this->t('Unrestricted');
}
/**
* {@inheritdoc}
*/
public function access(AccountInterface $account) {
// No access control.
return TRUE;
}
/**
* {@inheritdoc}
*/
public function alterRouteDefinition(Route $route) {
$route->setRequirement('_access', 'TRUE');
}
}

View file

@ -0,0 +1,129 @@
<?php
namespace Drupal\views\Plugin\views\area;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\ViewExecutable;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\Plugin\views\HandlerBase;
/**
* @defgroup views_area_handlers Views area handler plugins
* @{
* Plugins governing areas of views, such as header, footer, and empty text.
*
* Area handler plugins extend \Drupal\views\Plugin\views\area\AreaPluginBase.
* They must be annotated with \Drupal\views\Annotation\ViewsArea annotation,
* and they must be in namespace directory Plugin\views\area.
*
* @ingroup views_plugins
* @see plugin_api
*/
/**
* Base class for area handler plugins.
*/
abstract class AreaPluginBase extends HandlerBase {
/**
* The type of this area handler, i.e. 'header', 'footer', or 'empty'.
*
* @var string
*/
public $areaType;
/**
* Overrides Drupal\views\Plugin\views\HandlerBase::init().
*
* Make sure that no result area handlers are set to be shown when the result
* is empty.
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
if ($this->areaType == 'empty') {
$this->options['empty'] = TRUE;
}
}
/**
* {@inheritdoc}
*/
public function usesGroupBy() {
return FALSE;
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$this->definition['field'] = !empty($this->definition['field']) ? $this->definition['field'] : '';
$label = !empty($this->definition['label']) ? $this->definition['label'] : $this->definition['field'];
$options['admin_label']['default'] = $label;
$options['empty'] = array('default' => FALSE);
return $options;
}
/**
* {@inheritdoc}
*/
public function adminSummary() {
return $this->adminLabel();
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
if ($form_state->get('type') != 'empty') {
$form['empty'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Display even if view has no result'),
'#default_value' => isset($this->options['empty']) ? $this->options['empty'] : 0,
);
}
}
/**
* Performs any operations needed before full rendering.
*
* @param array $results
* The results of the view.
*/
public function preRender(array $results) {
}
/**
* Render the area.
*
* @param bool $empty
* (optional) Indicator if view result is empty or not. Defaults to FALSE.
*
* @return array
* In any case we need a valid Drupal render array to return.
*/
public abstract function render($empty = FALSE);
/**
* Does that area have nothing to show.
*
* This method should be overridden by more complex handlers where the output
* is not static and maybe itself be empty if it's rendered.
*
* @return bool
* Return TRUE if the area is empty, else FALSE.
*/
public function isEmpty() {
return empty($this->options['empty']);
}
}
/**
* @}
*/

View file

@ -0,0 +1,25 @@
<?php
namespace Drupal\views\Plugin\views\area;
use Drupal\views\Plugin\views\BrokenHandlerTrait;
/**
* A special handler to take the place of missing or broken handlers.
*
* @ingroup views_area_handlers
*
* @ViewsArea("broken")
*/
class Broken extends AreaPluginBase {
use BrokenHandlerTrait;
/**
* {@inheritdoc}
*/
public function render($empty = FALSE) {
// Simply render nothing by returning an empty render array.
return array();
}
}

View file

@ -0,0 +1,198 @@
<?php
namespace Drupal\views\Plugin\views\area;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides an area handler which renders an entity in a certain view mode.
*
* @ingroup views_area_handlers
*
* @ViewsArea("entity")
*/
class Entity extends TokenizeAreaPluginBase {
/**
* Stores the entity type of the result entities.
*
* @var string
*/
protected $entityType;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a new Entity instance.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityManagerInterface $entity_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);
$this->entityType = $this->definition['entity_type'];
}
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
// Per default we enable tokenize, as this is the most common use case for
// this handler.
$options['tokenize']['default'] = TRUE;
// Contains the config target identifier for the entity.
$options['target'] = ['default' => ''];
$options['view_mode'] = ['default' => 'default'];
$options['bypass_access'] = ['default' => FALSE];
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
$form['view_mode'] = array(
'#type' => 'select',
'#options' => $this->entityManager->getViewModeOptions($this->entityType),
'#title' => $this->t('View mode'),
'#default_value' => $this->options['view_mode'],
);
$label = $this->entityManager->getDefinition($this->entityType)->getLabel();
$target = $this->options['target'];
// If the target does not contain tokens, try to load the entity and
// display the entity ID to the admin form user.
// @todo Use a method to check for tokens in
// https://www.drupal.org/node/2396607.
if (strpos($this->options['target'], '{{') === FALSE) {
// @todo If the entity does not exist, this will will show the config
// target identifier. Decide if this is the correct behavior in
// https://www.drupal.org/node/2415391.
if ($target_entity = $this->entityManager->loadEntityByConfigTarget($this->entityType, $this->options['target'])) {
$target = $target_entity->id();
}
}
$form['target'] = [
'#title' => $this->t('@entity_type_label ID', ['@entity_type_label' => $label]),
'#type' => 'textfield',
'#default_value' => $target,
];
$form['bypass_access'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Bypass access checks'),
'#description' => $this->t('If enabled, access permissions for rendering the entity are not checked.'),
'#default_value' => !empty($this->options['bypass_access']),
);
}
/**
* {@inheritdoc}
*/
public function submitOptionsForm(&$form, FormStateInterface $form_state) {
parent::submitOptionsForm($form, $form_state);
// Load the referenced entity and store its config target identifier if
// the target does not contains tokens.
// @todo Use a method to check for tokens in
// https://www.drupal.org/node/2396607.
$options = $form_state->getValue('options');
if (strpos($options['target'], '{{') === FALSE) {
if ($entity = $this->entityManager->getStorage($this->entityType)->load($options['target'])) {
$options['target'] = $entity->getConfigTarget();
}
$form_state->setValue('options', $options);
}
}
/**
* {@inheritdoc}
*/
public function render($empty = FALSE) {
if (!$empty || !empty($this->options['empty'])) {
// @todo Use a method to check for tokens in
// https://www.drupal.org/node/2396607.
if (strpos($this->options['target'], '{{') !== FALSE) {
// We cast as we need the integer/string value provided by the
// ::tokenizeValue() call.
$target_id = (string) $this->tokenizeValue($this->options['target']);
if ($entity = $this->entityManager->getStorage($this->entityType)->load($target_id)) {
$target_entity = $entity;
}
}
else {
if ($entity = $this->entityManager->loadEntityByConfigTarget($this->entityType, $this->options['target'])) {
$target_entity = $entity;
}
}
if (isset($target_entity) && (!empty($this->options['bypass_access']) || $target_entity->access('view'))) {
$view_builder = $this->entityManager->getViewBuilder($this->entityType);
return $view_builder->view($target_entity, $this->options['view_mode']);
}
}
return [];
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
// Ensure that we don't add dependencies for placeholders.
// @todo Use a method to check for tokens in
// https://www.drupal.org/node/2396607.
if (strpos($this->options['target'], '{{') === FALSE) {
if ($entity = $this->entityManager->loadEntityByConfigTarget($this->entityType, $this->options['target'])) {
$dependencies[$this->entityManager->getDefinition($this->entityType)->getConfigDependencyKey()][] = $entity->getConfigDependencyName();
}
}
return $dependencies;
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\views\Plugin\views\area;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\HttpFoundation\Response;
/**
* Alter the HTTP response status code used by the view.
*
* @ingroup views_area_handlers
*
* @ViewsArea("http_status_code")
*/
class HTTPStatusCode extends AreaPluginBase {
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
$options['status_code'] = array('default' => 200);
return $options;
}
/**
* {@inheritdoc}
*/
public function buildOptionsForm(&$form, FormStateInterface $form_state) {
parent::buildOptionsForm($form, $form_state);
// Get all possible status codes defined by symfony.
$options = Response::$statusTexts;
// Move 403/404/500 to the top.
$options = array(
'404' => $options['404'],
'403' => $options['403'],
'500' => $options['500'],
) + $options;
// Add the HTTP status code, so it's easier for people to find it.
array_walk($options, function($title, $code) use(&$options) {
$options[$code] = $this->t('@code (@title)', array('@code' => $code, '@title' => $title));
});
$form['status_code'] = array(
'#title' => $this->t('HTTP status code'),
'#type' => 'select',
'#default_value' => $this->options['status_code'],
'#options' => $options,
);
}
/**
* {@inheritdoc}
*/
function render($empty = FALSE) {
if (!$empty || !empty($this->options['empty'])) {
$build['#attached']['http_header'][] = ['Status', $this->options['status_code']];
return $build;
}
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Drupal\views\Plugin\views\area;
/**
* Provides an area for messages.
*
* @ingroup views_area_handlers
*
* @ViewsArea("messages")
*/
class Messages extends AreaPluginBase {
/**
* {@inheritdoc}
*/
protected function defineOptions() {
$options = parent::defineOptions();
// Set the default to TRUE so it shows on empty pages by default.
$options['empty']['default'] = TRUE;
return $options;
}
/**
* {@inheritdoc}
*/
public function render($empty = FALSE) {
if (!$empty || !empty($this->options['empty'])) {
return array(
'#type' => 'status_messages',
);
}
return array();
}
}

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