upgrades core to 8.4.2

This commit is contained in:
Bachir Soussi Chiadmi
2017-11-14 16:09:54 +01:00
parent bd60eff9b3
commit c2b4e25be4
3504 changed files with 140306 additions and 38684 deletions
@@ -0,0 +1,51 @@
langcode: en
status: true
dependencies:
config:
- field.field.media.file.field_media_file
- media.type.file
module:
- file
id: media.file.default
targetEntityType: media
bundle: file
mode: default
content:
created:
type: datetime_timestamp
weight: 10
region: content
settings: { }
third_party_settings: { }
field_media_file:
settings:
progress_indicator: throbber
third_party_settings: { }
type: file_generic
weight: 26
region: content
name:
type: string_textfield
weight: -5
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
status:
type: boolean_checkbox
settings:
display_label: true
weight: 100
region: content
third_party_settings: { }
uid:
type: entity_reference_autocomplete
weight: 5
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
region: content
third_party_settings: { }
hidden: { }
@@ -0,0 +1,53 @@
langcode: en
status: true
dependencies:
config:
- field.field.media.image.field_media_image
- image.style.thumbnail
- media.type.image
module:
- image
id: media.image.default
targetEntityType: media
bundle: image
mode: default
content:
created:
type: datetime_timestamp
weight: 10
region: content
settings: { }
third_party_settings: { }
field_media_image:
settings:
progress_indicator: throbber
preview_image_style: thumbnail
third_party_settings: { }
type: image_image
weight: 26
region: content
name:
type: string_textfield
weight: -5
region: content
settings:
size: 60
placeholder: ''
third_party_settings: { }
status:
type: boolean_checkbox
settings:
display_label: true
weight: 100
region: content
third_party_settings: { }
uid:
type: entity_reference_autocomplete
weight: 5
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
region: content
third_party_settings: { }
hidden: { }
@@ -0,0 +1,24 @@
langcode: en
status: true
dependencies:
config:
- field.field.media.file.field_media_file
- media.type.file
module:
- file
id: media.file.default
targetEntityType: media
bundle: file
mode: default
content:
field_media_file:
label: visually_hidden
settings: { }
third_party_settings: { }
type: file_default
weight: 0
region: content
hidden:
created: true
thumbnail: true
uid: true
@@ -0,0 +1,27 @@
langcode: en
status: true
dependencies:
config:
- field.field.media.image.field_media_image
- image.style.medium
- media.type.image
module:
- image
id: media.image.default
targetEntityType: media
bundle: image
mode: default
content:
field_media_image:
label: visually_hidden
settings:
image_style: medium
image_link: file
third_party_settings: { }
type: image
weight: 0
region: content
hidden:
created: true
thumbnail: true
uid: true
@@ -0,0 +1,9 @@
langcode: en
status: false
dependencies:
module:
- media
id: media.full
label: 'Full content'
targetEntityType: media
cache: true
@@ -0,0 +1,29 @@
langcode: en
status: true
dependencies:
config:
- field.storage.media.field_media_file
- media.type.file
enforced:
module:
- media
module:
- file
id: media.file.field_media_file
field_name: field_media_file
entity_type: media
bundle: file
label: File
description: ''
required: true
translatable: true
default_value: { }
default_value_callback: ''
settings:
file_directory: '[date:custom:Y]-[date:custom:m]'
file_extensions: 'txt rtf doc docx ppt pptx xls xlsx pdf odf odg odp ods odt fodt fods fodp fodg key numbers pages'
max_filesize: ''
handler: 'default:file'
handler_settings: { }
description_field: false
field_type: file
@@ -0,0 +1,40 @@
langcode: en
status: true
dependencies:
config:
- field.storage.media.field_media_image
- media.type.image
enforced:
module:
- media
module:
- image
id: media.image.field_media_image
field_name: field_media_image
entity_type: media
bundle: image
label: Image
description: ''
required: true
translatable: true
default_value: { }
default_value_callback: ''
settings:
alt_field: true
alt_field_required: true
title_field: false
title_field_required: false
max_resolution: ''
min_resolution: ''
default_image:
uuid: null
alt: ''
title: ''
width: null
height: null
file_directory: '[date:custom:Y]-[date:custom:m]'
file_extensions: 'png gif jpg jpeg'
max_filesize: ''
handler: 'default:file'
handler_settings: { }
field_type: image
@@ -0,0 +1,25 @@
langcode: en
status: true
dependencies:
enforced:
module:
- media
module:
- file
- media
id: media.field_media_file
field_name: field_media_file
entity_type: media
type: file
settings:
uri_scheme: public
target_type: file
display_field: false
display_default: false
module: file
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1,32 @@
langcode: en
status: true
dependencies:
enforced:
module:
- media
module:
- file
- image
- media
id: media.field_media_image
field_name: field_media_image
entity_type: media
type: image
settings:
default_image:
uuid: null
alt: ''
title: ''
width: null
height: null
target_type: file
display_field: false
display_default: false
uri_scheme: public
module: image
locked: false
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
@@ -0,0 +1 @@
icon_base_uri: 'public://media-icons/generic'
@@ -0,0 +1,12 @@
langcode: en
status: true
dependencies: { }
id: file
label: File
description: "Use local files for reusable media."
source: file
queue_thumbnail_downloads: false
new_revision: true
source_configuration:
source_field: field_media_file
field_map: { }
@@ -0,0 +1,12 @@
langcode: en
status: true
dependencies: { }
id: image
label: Image
description: "Use local images for reusable media."
source: image
queue_thumbnail_downloads: false
new_revision: true
source_configuration:
source_field: field_media_image
field_map: { }
@@ -0,0 +1,801 @@
langcode: en
status: true
dependencies:
module:
- image
- media
- user
id: media
label: Media
module: views
description: ''
tag: ''
base_table: media_field_data
base_field: mid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access media overview'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Filter
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 50
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: table
options:
grouping: { }
row_class: ''
default_row_class: true
override: true
sticky: false
caption: ''
summary: ''
description: ''
columns:
name: name
bundle: bundle
changed: changed
uid: uid
status: status
thumbnail__target_id: thumbnail__target_id
info:
name:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
bundle:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
changed:
sortable: true
default_sort_order: desc
align: ''
separator: ''
empty_column: false
responsive: ''
uid:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
status:
sortable: true
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
thumbnail__target_id:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
default: changed
empty_table: true
row:
type: fields
fields:
thumbnail__target_id:
id: thumbnail__target_id
table: media_field_data
field: thumbnail__target_id
relationship: none
group_type: group
admin_label: ''
label: Thumbnail
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: target_id
type: image
settings:
image_style: thumbnail
image_link: ''
group_column: ''
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: media
entity_field: thumbnail
plugin_id: field
name:
id: name
table: media_field_data
field: name
entity_type: media
entity_field: media
alter:
alter_text: false
make_link: false
absolute: false
trim: false
word_boundary: false
ellipsis: false
strip_tags: false
html: false
hide_empty: false
empty_zero: false
settings:
link_to_entity: true
plugin_id: field
relationship: none
group_type: group
admin_label: ''
label: 'Media name'
exclude: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_alter_empty: true
click_sort_column: value
type: string
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
bundle:
id: bundle
table: media_field_data
field: bundle
relationship: none
group_type: group
admin_label: ''
label: Source
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: target_id
type: entity_reference_label
settings:
link: false
group_column: target_id
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: media
entity_field: bundle
plugin_id: field
uid:
id: uid
table: media_field_data
field: uid
relationship: none
group_type: group
admin_label: ''
label: Author
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: target_id
type: entity_reference_label
settings:
link: true
group_column: target_id
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: media
entity_field: uid
plugin_id: field
status:
id: status
table: media_field_data
field: status
relationship: none
group_type: group
admin_label: ''
label: Status
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: boolean
settings:
format: custom
format_custom_true: Published
format_custom_false: Unpublished
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: media
entity_field: status
plugin_id: field
changed:
id: changed
table: media_field_data
field: changed
relationship: none
group_type: group
admin_label: ''
label: Updated
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: timestamp
settings:
date_format: short
custom_date_format: ''
timezone: ''
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: media
entity_field: changed
plugin_id: field
operations:
id: operations
table: media
field: operations
relationship: none
group_type: group
admin_label: ''
label: Operations
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
destination: true
entity_type: media
plugin_id: entity_operations
filters:
name:
id: name
table: media_field_data
field: name
relationship: none
group_type: group
admin_label: ''
operator: contains
value: ''
group: 1
exposed: true
expose:
operator_id: name_op
label: 'Media name'
description: ''
use_operator: false
operator: name_op
identifier: name
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: media
entity_field: name
plugin_id: string
bundle:
id: bundle
table: media_field_data
field: bundle
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: bundle_op
label: Source
description: ''
use_operator: false
operator: bundle_op
identifier: source
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
reduce: false
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: media
entity_field: bundle
plugin_id: bundle
status:
id: status
table: media_field_data
field: status
relationship: none
group_type: group
admin_label: ''
operator: '='
value: '1'
group: 1
exposed: true
expose:
operator_id: ''
label: 'True'
description: null
use_operator: false
operator: status_op
identifier: status
required: true
remember: false
multiple: false
remember_roles:
authenticated: authenticated
is_grouped: true
group_info:
label: 'Published status'
description: ''
identifier: status
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items:
1:
title: Published
operator: '='
value: '1'
2:
title: Unpublished
operator: '='
value: '0'
plugin_id: boolean
entity_type: media
entity_field: status
langcode:
id: langcode
table: media_field_data
field: langcode
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: langcode_op
label: Language
description: ''
use_operator: false
operator: langcode_op
identifier: langcode
required: false
remember: false
multiple: false
remember_roles:
authenticated: authenticated
anonymous: '0'
administrator: '0'
reduce: false
is_grouped: false
group_info:
label: ''
description: ''
identifier: ''
optional: true
widget: select
multiple: false
remember: false
default_group: All
default_group_multiple: { }
group_items: { }
entity_type: media
entity_field: langcode
plugin_id: language
sorts:
created:
id: created
table: media_field_data
field: created
order: DESC
entity_type: media
entity_field: created
plugin_id: date
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
title: Media
header: { }
footer: { }
empty:
area_text_custom:
id: area_text_custom
table: views
field: area_text_custom
relationship: none
group_type: group
admin_label: ''
empty: true
tokenize: false
content: 'No content available.'
plugin_id: text_custom
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- user.permissions
tags: { }
media_page_list:
display_plugin: page
id: media_page_list
display_title: Media
position: 1
display_options:
display_extenders: { }
path: admin/content/media
menu:
type: tab
title: Media
description: ''
expanded: false
parent: ''
weight: 0
context: '0'
menu_name: main
display_description: ''
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- url.query_args
- user.permissions
tags: { }
@@ -0,0 +1,60 @@
media.settings:
type: config_object
label: 'Media settings'
mapping:
icon_base_uri:
type: string
label: 'Full URI to a folder where the media icons will be installed'
media.type.*:
type: config_entity
label: 'Media type'
mapping:
id:
type: string
label: 'Machine name'
label:
type: label
label: 'Name'
description:
type: text
label: 'Description'
source:
type: string
label: 'Source'
source_configuration:
type: media.source.[%parent.source]
queue_thumbnail_downloads:
type: boolean
label: 'Whether the thumbnail downloads should be queued'
new_revision:
type: boolean
label: 'Whether a new revision should be created by default'
field_map:
type: sequence
label: 'Field map'
sequence:
type: string
field.formatter.settings.media_thumbnail:
type: field.formatter.settings.image
label: 'Media thumbnail field display format settings'
media.source.*:
type: mapping
label: 'Media source settings'
media.source.file:
type: media.source.field_aware
label: '"File" media source configuration'
media.source.image:
type: media.source.field_aware
label: '"Image" media source configuration'
media.source.field_aware:
type: mapping
mapping:
source_field:
type: string
label: 'Source field'
Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

+36
View File
@@ -0,0 +1,36 @@
/**
* @file
* Defines Javascript behaviors for the media form.
*/
(function ($, Drupal) {
/**
* Behaviors for summaries for tabs in the media edit form.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches summary behavior for tabs in the media edit form.
*/
Drupal.behaviors.mediaFormSummaries = {
attach(context) {
const $context = $(context);
$context.find('.media-form-author').drupalSetSummary((context) => {
const $authorContext = $(context);
const name = $authorContext.find('.field--name-uid input').val();
const date = $authorContext.find('.field--name-created input').val();
if (name && date) {
return Drupal.t('By @name on @date', { '@name': name, '@date': date });
}
else if (name) {
return Drupal.t('By @name', { '@name': name });
}
else if (date) {
return Drupal.t('Authored on @date', { '@date': date });
}
});
},
};
}(jQuery, Drupal));
+28
View File
@@ -0,0 +1,28 @@
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal) {
Drupal.behaviors.mediaFormSummaries = {
attach: function attach(context) {
var $context = $(context);
$context.find('.media-form-author').drupalSetSummary(function (context) {
var $authorContext = $(context);
var name = $authorContext.find('.field--name-uid input').val();
var date = $authorContext.find('.field--name-created input').val();
if (name && date) {
return Drupal.t('By @name on @date', { '@name': name, '@date': date });
} else if (name) {
return Drupal.t('By @name', { '@name': name });
} else if (date) {
return Drupal.t('Authored on @date', { '@date': date });
}
});
}
};
})(jQuery, Drupal);
+42
View File
@@ -0,0 +1,42 @@
/**
* @file
* Defines JavaScript behaviors for the media type form.
*/
(function ($, Drupal) {
/**
* Behaviors for setting summaries on media type form.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches summary behaviors on media type edit forms.
*/
Drupal.behaviors.mediaTypeFormSummaries = {
attach(context) {
const $context = $(context);
// Provide the vertical tab summaries.
$context.find('#edit-workflow').drupalSetSummary((context) => {
const vals = [];
$(context).find('input[name^="options"]:checked').parent().each(function () {
vals.push(Drupal.checkPlain($(this).find('label').text()));
});
if (!$(context).find('#edit-options-status').is(':checked')) {
vals.unshift(Drupal.t('Not published'));
}
return vals.join(', ');
});
$(context).find('#edit-language').drupalSetSummary((context) => {
const vals = [];
vals.push($(context).find('.js-form-item-language-configuration-langcode select option:selected').text());
$(context).find('input:checked').next('label').each(function () {
vals.push(Drupal.checkPlain($(this).text()));
});
return vals.join(', ');
});
},
};
}(jQuery, Drupal));
+36
View File
@@ -0,0 +1,36 @@
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal) {
Drupal.behaviors.mediaTypeFormSummaries = {
attach: function attach(context) {
var $context = $(context);
$context.find('#edit-workflow').drupalSetSummary(function (context) {
var vals = [];
$(context).find('input[name^="options"]:checked').parent().each(function () {
vals.push(Drupal.checkPlain($(this).find('label').text()));
});
if (!$(context).find('#edit-options-status').is(':checked')) {
vals.unshift(Drupal.t('Not published'));
}
return vals.join(', ');
});
$(context).find('#edit-language').drupalSetSummary(function (context) {
var vals = [];
vals.push($(context).find('.js-form-item-language-configuration-langcode select option:selected').text());
$(context).find('input:checked').next('label').each(function () {
vals.push(Drupal.checkPlain($(this).text()));
});
return vals.join(', ');
});
}
};
})(jQuery, Drupal);
+25
View File
@@ -0,0 +1,25 @@
<?php
/**
* @file
* Hooks related to Media and its plugins.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Alters the information provided in \Drupal\media\Annotation\MediaSource.
*
* @param array $sources
* The array of media source plugin definitions, keyed by plugin ID.
*/
function hook_media_source_info_alter(array &$sources) {
$sources['youtube']['label'] = t('Youtube rocks!');
}
/**
* @} End of "addtogroup hooks".
*/
+17
View File
@@ -0,0 +1,17 @@
name: Media
description: 'Create reusable media.'
type: module
package: Core
# version: VERSION
# core: 8.x
hidden: true
dependencies:
- file
- image
- user
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
+79
View File
@@ -0,0 +1,79 @@
<?php
/**
* @file
* Install, uninstall and update hooks for Media module.
*/
use Drupal\user\RoleInterface;
/**
* Implements hook_install().
*/
function media_install() {
$source = drupal_get_path('module', 'media') . '/images/icons';
$destination = \Drupal::config('media.settings')->get('icon_base_uri');
file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$files = file_scan_directory($source, '/.*\.(svg|png|jpg|jpeg|gif)$/');
foreach ($files as $file) {
// When reinstalling the media module we don't want to copy the icons when
// they already exist. The icons could be replaced (by a contrib module or
// manually), so we don't want to replace the existing files. Removing the
// files when we uninstall could also be a problem if the files are
// referenced somewhere else. Since showing an error that it was not
// possible to copy the files is also confusing, we silently do nothing.
if (!file_exists($destination . DIRECTORY_SEPARATOR . $file->filename)) {
file_unmanaged_copy($file->uri, $destination, FILE_EXISTS_ERROR);
}
}
// Grant the "view media" permission to all users by default.
if (\Drupal::moduleHandler()->moduleExists('user')) {
user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, ['view media']);
user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, ['view media']);
}
}
/**
* Implements hook_requirements().
*/
function media_requirements($phase) {
$requirements = [];
if ($phase == 'install') {
$destination = 'public://media-icons/generic';
file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$is_writable = is_writable($destination);
$is_directory = is_dir($destination);
if (!$is_writable || !$is_directory) {
if (!$is_directory) {
$error = t('The directory %directory does not exist.', ['%directory' => $destination]);
}
else {
$error = t('The directory %directory is not writable.', ['%directory' => $destination]);
}
$description = t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', [':handbook_url' => 'https://www.drupal.org/server-permissions']);
if (!empty($error)) {
$description = $error . ' ' . $description;
$requirements['media']['description'] = $description;
$requirements['media']['severity'] = REQUIREMENT_ERROR;
}
}
// Prevent installation if the 1.x branch of the contrib module is enabled.
if (\Drupal::moduleHandler()->moduleExists('media_entity')) {
$info = system_get_info('module', 'media_entity');
if (version_compare($info['version'], '8.x-2') < 0) {
$requirements['media_module_incompatibility'] = [
'title' => t('Media'),
'description' => t('The Media module is not compatible with contrib <a href=":url">Media Entity</a> 1.x branch. Please check the 2.x branch of that module for an upgrade path.', [
':url' => 'https://drupal.org/project/media_entity',
]),
'severity' => REQUIREMENT_ERROR,
];
}
}
}
return $requirements;
}
+13
View File
@@ -0,0 +1,13 @@
form:
version: VERSION
js:
js/form.js: {}
dependencies:
- core/drupal.form
type_form:
version: VERSION
js:
js/type_form.js: {}
dependencies:
- core/drupal.form
+12
View File
@@ -0,0 +1,12 @@
media.bundle_add:
route_name: entity.media_type.add_form
title: 'Add media type'
appears_on:
- entity.media_type.collection
media.add:
route_name: entity.media.add_page
title: 'Add media'
weight: 10
appears_on:
- view.media.media_page_list
@@ -0,0 +1,10 @@
entity.media.edit_form:
route_name: entity.media.edit_form
group: media
title: Edit
entity.media.delete_form:
route_name: entity.media.delete_form
group: media
title: Delete
weight: 10
+6
View File
@@ -0,0 +1,6 @@
entity.media_type.collection:
title: 'Media types'
parent: system.admin_structure
description: 'Manage media types.'
route_name: entity.media_type.collection
+25
View File
@@ -0,0 +1,25 @@
entity.media.canonical:
title: View
route_name: entity.media.canonical
base_route: entity.media.canonical
entity.media.edit_form:
title: Edit
route_name: entity.media.edit_form
base_route: entity.media.canonical
entity.media.delete_form:
title: Delete
route_name: entity.media.delete_form
base_route: entity.media.canonical
weight: 10
entity.media_type.edit_form:
title: Edit
route_name: entity.media_type.edit_form
base_route: entity.media_type.edit_form
entity.media_type.collection:
title: List
route_name: entity.media_type.collection
base_route: entity.media_type.collection
+98
View File
@@ -0,0 +1,98 @@
<?php
/**
* @file
* Provides media items.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\FieldConfigInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
/**
* Implements hook_help().
*/
function media_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.media':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Media module manages the creation, editing, deletion, settings and display of media. Items are typically images, documents, slideshows, YouTube videos, tweets, Instagram photos, etc. You can reference media items from any other content on your site. For more information, see the <a href=":media">online documentation for the Media module</a>.', [':media' => 'https://www.drupal.org/docs/8/core/modules/media']) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Creating media items') . '</dt>';
$output .= '<dd>' . t('When a new media item is created, the Media module records basic information about it, including the author, date of creation, and the <a href=":media-type">media type</a>. It also manages the <em>publishing options</em>, which define whether or not the item is published. Default settings can be configured for each type of media on your site.', [':media-type' => Url::fromRoute('entity.media_type.collection')->toString()]) . '</dd>';
$output .= '<dt>' . t('Creating custom media types') . '</dt>';
$output .= '<dd>' . t('The Media module gives users with the <em>Administer media types</em> permission the ability to <a href=":media-new">create new media types</a> in addition to the default ones already configured. Each media type has an associated media source (such as the image source) which support thumbnail generation and metadata extraction. Fields managed by the <a href=":field">Field module</a> may be added for storing that metadata, such as width and height, as well as any other associated values.', [':media-new' => Url::fromRoute('entity.media_type.add_form')->toString(), ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</dd>';
$output .= '<dt>' . t('Creating revisions') . '</dt>';
$output .= '<dd>' . t('The Media module also enables you to create multiple versions of any media item, and revert to older versions using the <em>Revision information</em> settings.') . '</dd>';
$output .= '<dt>' . t('User permissions') . '</dt>';
$output .= '<dd>' . t('The Media module makes a number of permissions available, which can be set by role on the <a href=":permissions">permissions page</a>.', [':permissions' => Url::fromRoute('user.admin_permissions', [], ['fragment' => 'module-media'])->toString()]) . '</dd>';
$output .= '</dl>';
return $output;
}
}
/**
* Implements hook_theme().
*/
function media_theme() {
return [
'media' => [
'render element' => 'elements',
],
];
}
/**
* Implements hook_entity_access().
*/
function media_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
if ($operation === 'delete' && $entity instanceof FieldConfigInterface && $entity->getTargetEntityTypeId() === 'media') {
/** @var \Drupal\media\MediaTypeInterface $media_type */
$media_type = \Drupal::entityTypeManager()->getStorage('media_type')->load($entity->getTargetBundle());
return AccessResult::forbiddenIf($entity->id() === 'media.' . $media_type->id() . '.' . $media_type->getSource()->getConfiguration()['source_field']);
}
return AccessResult::neutral();
}
/**
* Implements hook_theme_suggestions_HOOK().
*/
function media_theme_suggestions_media(array $variables) {
$suggestions = [];
$media = $variables['elements']['#media'];
$sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
$suggestions[] = 'media__' . $sanitized_view_mode;
$suggestions[] = 'media__' . $media->bundle();
$suggestions[] = 'media__' . $media->bundle() . '__' . $sanitized_view_mode;
return $suggestions;
}
/**
* Prepares variables for media templates.
*
* Default template: media.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An array of elements to display in view mode.
* - media: The media item.
* - name: The label for the media item.
* - view_mode: View mode; e.g., 'full', 'teaser', etc.
*/
function template_preprocess_media(array &$variables) {
$variables['media'] = $variables['elements']['#media'];
$variables['view_mode'] = $variables['elements']['#view_mode'];
$variables['name'] = $variables['media']->label();
// Helpful $content variable for templates.
foreach (Element::children($variables['elements']) as $key) {
$variables['content'][$key] = $variables['elements'][$key];
}
}
+33
View File
@@ -0,0 +1,33 @@
administer media:
title: 'Administer media'
restrict access: TRUE
administer media types:
title: 'Administer media types'
restrict access: TRUE
view media:
title: 'View media'
update media:
title: 'Update own media'
update any media:
title: 'Update any media'
delete media:
title: 'Delete own media'
delete any media:
title: 'Delete any media'
create media:
title: 'Create media'
view all media revisions:
title: 'View all media revisions'
description: 'To view a revision, you also need permission to view the media item.'
access media overview:
title: 'Access media overview'
description: 'Users with this permission can access the media overview page.'
+21
View File
@@ -0,0 +1,21 @@
entity.media.multiple_delete_confirm:
path: '/admin/content/media/delete'
defaults:
_form: '\Drupal\media\Form\MediaDeleteMultipleConfirmForm'
requirements:
_permission: 'administer media+delete any media'
entity.media.revision:
path: '/media/{media}/revisions/{media_revision}/view'
defaults:
_controller: '\Drupal\Core\Entity\Controller\EntityViewController::viewRevision'
_title_callback: '\Drupal\Core\Entity\Controller\EntityController::title'
options:
parameters:
media:
type: entity:media
media_revision:
type: entity_revision:media
requirements:
_access_media_revision: 'view'
media: \d+
+10
View File
@@ -0,0 +1,10 @@
services:
plugin.manager.media.source:
class: Drupal\media\MediaSourceManager
parent: default_plugin_manager
access_check.media.revision:
class: Drupal\media\Access\MediaRevisionAccessCheck
arguments: ['@entity_type.manager']
tags:
- { name: access_check, applies_to: _access_media_revision }
@@ -0,0 +1,151 @@
<?php
namespace Drupal\media\Access;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Routing\Access\AccessInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\media\MediaInterface;
use Symfony\Component\Routing\Route;
/**
* Provides an access checker for media item revisions.
*
* @ingroup media_access
*/
class MediaRevisionAccessCheck implements AccessInterface {
/**
* The media storage.
*
* @var \Drupal\Core\Entity\ContentEntityStorageInterface
*/
protected $mediaStorage;
/**
* The media access control handler.
*
* @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface
*/
protected $mediaAccess;
/**
* A static cache of access checks.
*
* @var array
*/
protected $access = [];
/**
* Constructs a new MediaRevisionAccessCheck.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->mediaStorage = $entity_type_manager->getStorage('media');
$this->mediaAccess = $entity_type_manager->getAccessControlHandler('media');
}
/**
* Checks routing access for the media item revision.
*
* @param \Symfony\Component\Routing\Route $route
* The route to check against.
* @param \Drupal\Core\Session\AccountInterface $account
* The currently logged in account.
* @param int $media_revision
* (optional) The media item revision ID. If not specified, but $media is,
* access is checked for that object's revision.
* @param \Drupal\media\MediaInterface $media
* (optional) A media item. Used for checking access to a media items
* default revision when $media_revision is unspecified. Ignored when
* $media_revision is specified. If neither $media_revision nor $media are
* specified, then access is denied.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(Route $route, AccountInterface $account, $media_revision = NULL, MediaInterface $media = NULL) {
if ($media_revision) {
$media = $this->mediaStorage->loadRevision($media_revision);
}
$operation = $route->getRequirement('_access_media_revision');
return AccessResult::allowedIf($media && $this->checkAccess($media, $account, $operation))->cachePerPermissions()->addCacheableDependency($media);
}
/**
* Checks media item revision access.
*
* @param \Drupal\media\MediaInterface $media
* The media item to check.
* @param \Drupal\Core\Session\AccountInterface $account
* A user object representing the user for whom the operation is to be
* performed.
* @param string $op
* (optional) The specific operation being checked. Defaults to 'view'.
*
* @return bool
* TRUE if the operation may be performed, FALSE otherwise.
*/
public function checkAccess(MediaInterface $media, AccountInterface $account, $op = 'view') {
if (!$media || $op !== 'view') {
// If there was no media to check against, or the $op was not one of the
// supported ones, we return access denied.
return FALSE;
}
// Statically cache access by revision ID, language code, user account ID,
// and operation.
$langcode = $media->language()->getId();
$cid = $media->getRevisionId() . ':' . $langcode . ':' . $account->id() . ':' . $op;
if (!isset($this->access[$cid])) {
// Perform basic permission checks first.
if (!$account->hasPermission('view all media revisions') && !$account->hasPermission('administer media')) {
$this->access[$cid] = FALSE;
return FALSE;
}
// There should be at least two revisions. If the revision ID of the
// given media item and the revision ID of the default revision differ,
// then we already have two different revisions so there is no need for a
// separate database check.
if ($media->isDefaultRevision() && ($this->countDefaultLanguageRevisions($media) == 1)) {
$this->access[$cid] = FALSE;
}
elseif ($account->hasPermission('administer media')) {
$this->access[$cid] = TRUE;
}
else {
// First check the access to the default revision and finally, if the
// media passed in is not the default revision then access to that, too.
$this->access[$cid] = $this->mediaAccess->access($this->mediaStorage->load($media->id()), $op, $account) && ($media->isDefaultRevision() || $this->mediaAccess->access($media, $op, $account));
}
}
return $this->access[$cid];
}
/**
* Counts the number of revisions in the default language.
*
* @param \Drupal\media\MediaInterface $media
* The media item for which to to count the revisions.
*
* @return int
* The number of revisions in the default language.
*/
protected function countDefaultLanguageRevisions(MediaInterface $media) {
$entity_type = $media->getEntityType();
$count = $this->mediaStorage->getQuery()
->allRevisions()
->condition($entity_type->getKey('id'), $media->id())
->condition($entity_type->getKey('default_langcode'), 1)
->count()
->execute();
return $count;
}
}
@@ -0,0 +1,108 @@
<?php
namespace Drupal\media\Annotation;
use Drupal\Component\Annotation\Plugin;
/**
* Defines a media source plugin annotation object.
*
* Media sources are responsible for implementing all the logic for dealing
* with a particular type of media. They provide various universal and
* type-specific metadata about media of the type they handle.
*
* Plugin namespace: Plugin\media\Source
*
* For a working example, see \Drupal\media\Plugin\media\Source\File.
*
* @see \Drupal\media\MediaSourceInterface
* @see \Drupal\media\MediaSourceBase
* @see \Drupal\media\MediaSourceManager
* @see hook_media_source_info_alter()
* @see plugin_api
*
* @Annotation
*/
class MediaSource extends Plugin {
/**
* The plugin ID.
*
* @var string
*/
public $id;
/**
* The human-readable name of the media source.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $label;
/**
* A brief description of the media source.
*
* @var \Drupal\Core\Annotation\Translation
*
* @ingroup plugin_translatable
*/
public $description = '';
/**
* The field types that can be used as a source field for this media source.
*
* @var string[]
*/
public $allowed_field_types = [];
/**
* A filename for the default thumbnail.
*
* The thumbnails are placed in the directory defined by the config setting
* 'media.settings.icon_base_uri'. When using custom icons, make sure the
* module provides a hook_install() implementation to copy the custom icons
* to this directory. The media_install() function provides a clear example
* of how to do this.
*
* @var string
*
* @see media_install()
*/
public $default_thumbnail_filename = 'generic.png';
/**
* The metadata attribute name to provide the thumbnail URI.
*
* @var string
*/
public $thumbnail_uri_metadata_attribute = 'thumbnail_uri';
/**
* (optional) The metadata attribute name to provide the thumbnail alt.
*
* "Thumbnail" will be used if the attribute name is not provided.
*
* @var string|null
*/
public $thumbnail_alt_metadata_attribute;
/**
* (optional) The metadata attribute name to provide the thumbnail title.
*
* The name of the media entity will be used if the attribute name is not
* provided.
*
* @var string|null
*/
public $thumbnail_title_metadata_attribute;
/**
* The metadata attribute name to provide the default name.
*
* @var string
*/
public $default_name_metadata_attribute = 'default_name';
}
+510
View File
@@ -0,0 +1,510 @@
<?php
namespace Drupal\media\Entity;
use Drupal\Core\Entity\EditorialContentEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\media\MediaInterface;
use Drupal\media\MediaSourceEntityConstraintsInterface;
use Drupal\media\MediaSourceFieldConstraintsInterface;
use Drupal\user\UserInterface;
/**
* Defines the media entity class.
*
* @todo Remove default/fallback entity form operation when #2006348 is done.
* @see https://www.drupal.org/node/2006348.
*
* @ContentEntityType(
* id = "media",
* label = @Translation("Media"),
* label_singular = @Translation("media item"),
* label_plural = @Translation("media items"),
* label_count = @PluralTranslation(
* singular = "@count media item",
* plural = "@count media items"
* ),
* bundle_label = @Translation("Media type"),
* handlers = {
* "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
* "access" = "Drupal\media\MediaAccessControlHandler",
* "form" = {
* "default" = "Drupal\media\MediaForm",
* "add" = "Drupal\media\MediaForm",
* "edit" = "Drupal\media\MediaForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* },
* "translation" = "Drupal\content_translation\ContentTranslationHandler",
* "views_data" = "Drupal\media\MediaViewsData",
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
* }
* },
* base_table = "media",
* data_table = "media_field_data",
* revision_table = "media_revision",
* revision_data_table = "media_field_revision",
* translatable = TRUE,
* show_revision_ui = TRUE,
* entity_keys = {
* "id" = "mid",
* "revision" = "vid",
* "bundle" = "bundle",
* "label" = "name",
* "langcode" = "langcode",
* "uuid" = "uuid",
* "published" = "status",
* },
* revision_metadata_keys = {
* "revision_user" = "revision_user",
* "revision_created" = "revision_created",
* "revision_log_message" = "revision_log_message",
* },
* bundle_entity_type = "media_type",
* permission_granularity = "entity_type",
* admin_permission = "administer media",
* field_ui_base_route = "entity.media_type.edit_form",
* common_reference_target = TRUE,
* links = {
* "add-page" = "/media/add",
* "add-form" = "/media/add/{media_type}",
* "canonical" = "/media/{media}",
* "delete-form" = "/media/{media}/delete",
* "edit-form" = "/media/{media}/edit",
* "revision" = "/media/{media}/revisions/{media_revision}/view",
* }
* )
*/
class Media extends EditorialContentEntityBase implements MediaInterface {
use StringTranslationTrait;
/**
* {@inheritdoc}
*/
public function getName() {
$name = $this->get('name');
if ($name->isEmpty()) {
$media_source = $this->getSource();
return $media_source->getMetadata($this, $media_source->getPluginDefinition()['default_name_metadata_attribute']);
}
else {
return $name->value;
}
}
/**
* {@inheritdoc}
*/
public function label() {
return $this->getName();
}
/**
* {@inheritdoc}
*/
public function setName($name) {
return $this->set('name', $name);
}
/**
* {@inheritdoc}
*/
public function getCreatedTime() {
return $this->get('created')->value;
}
/**
* {@inheritdoc}
*/
public function setCreatedTime($timestamp) {
return $this->set('created', $timestamp);
}
/**
* {@inheritdoc}
*/
public function getOwner() {
return $this->get('uid')->entity;
}
/**
* {@inheritdoc}
*/
public function setOwner(UserInterface $account) {
return $this->set('uid', $account->id());
}
/**
* {@inheritdoc}
*/
public function getOwnerId() {
return $this->get('uid')->target_id;
}
/**
* {@inheritdoc}
*/
public function setOwnerId($uid) {
return $this->set('uid', $uid);
}
/**
* {@inheritdoc}
*/
public function getSource() {
return $this->bundle->entity->getSource();
}
/**
* Update the thumbnail for the media item.
*
* @param bool $from_queue
* Specifies whether the thumbnail update is triggered from the queue.
*
* @return \Drupal\media\MediaInterface
* The updated media item.
*
* @internal
*
* @todo There has been some disagreement about how to handle updates to
* thumbnails. We need to decide on what the API will be for this.
* https://www.drupal.org/node/2878119
*/
protected function updateThumbnail($from_queue = FALSE) {
$file_storage = \Drupal::service('entity_type.manager')->getStorage('file');
$thumbnail_uri = $this->getThumbnailUri($from_queue);
$existing = $file_storage->getQuery()
->condition('uri', $thumbnail_uri)
->execute();
if ($existing) {
$this->thumbnail->target_id = reset($existing);
}
else {
/** @var \Drupal\file\FileInterface $file */
$file = $file_storage->create(['uri' => $thumbnail_uri]);
if ($owner = $this->getOwner()) {
$file->setOwner($owner);
}
$file->setPermanent();
$file->save();
$this->thumbnail->target_id = $file->id();
}
// Set the thumbnail alt.
$media_source = $this->getSource();
$plugin_definition = $media_source->getPluginDefinition();
if (!empty($plugin_definition['thumbnail_alt_metadata_attribute'])) {
$this->thumbnail->alt = $media_source->getMetadata($this, $plugin_definition['thumbnail_alt_metadata_attribute']);
}
else {
$this->thumbnail->alt = $this->t('Thumbnail', [], ['langcode' => $this->langcode->value]);
}
// Set the thumbnail title.
if (!empty($plugin_definition['thumbnail_title_metadata_attribute'])) {
$this->thumbnail->title = $media_source->getMetadata($this, $plugin_definition['thumbnail_title_metadata_attribute']);
}
else {
$this->thumbnail->title = $this->label();
}
return $this;
}
/**
* Updates the queued thumbnail for the media item.
*
* @return \Drupal\media\MediaInterface
* The updated media item.
*
* @internal
*
* @todo If the need arises in contrib, consider making this a public API,
* by adding an interface that extends MediaInterface.
*/
public function updateQueuedThumbnail() {
$this->updateThumbnail(TRUE);
return $this;
}
/**
* Gets the URI for the thumbnail of a media item.
*
* If thumbnail fetching is queued, new media items will use the default
* thumbnail, and existing media items will use the current thumbnail, until
* the queue is processed and the updated thumbnail has been fetched.
* Otherwise, the new thumbnail will be fetched immediately.
*
* @param bool $from_queue
* Specifies whether the thumbnail is being fetched from the queue.
*
* @return string
* The file URI for the thumbnail of the media item.
*
* @internal
*/
protected function getThumbnailUri($from_queue) {
$thumbnails_queued = $this->bundle->entity->thumbnailDownloadsAreQueued();
if ($thumbnails_queued && $this->isNew()) {
$default_thumbnail_filename = $this->getSource()->getPluginDefinition()['default_thumbnail_filename'];
$thumbnail_uri = \Drupal::service('config.factory')->get('media.settings')->get('icon_base_uri') . '/' . $default_thumbnail_filename;
}
elseif ($thumbnails_queued && !$from_queue) {
$thumbnail_uri = $this->get('thumbnail')->entity->getFileUri();
}
else {
$thumbnail_uri = $this->getSource()->getMetadata($this, $this->getSource()->getPluginDefinition()['thumbnail_uri_metadata_attribute']);
}
return $thumbnail_uri;
}
/**
* Determines if the source field value has changed.
*
* @return bool
* TRUE if the source field value changed, FALSE otherwise.
*
* @internal
*/
protected function hasSourceFieldChanged() {
$source_field_name = $this->getSource()->getConfiguration()['source_field'];
$current_items = $this->get($source_field_name);
return isset($this->original) && !$current_items->equals($this->original->get($source_field_name));
}
/**
* Determines if the thumbnail should be updated for a media item.
*
* @param bool $is_new
* Specifies whether the media item is new.
*
* @return bool
* TRUE if the thumbnail should be updated, FALSE otherwise.
*/
protected function shouldUpdateThumbnail($is_new = FALSE) {
// Update thumbnail if we don't have a thumbnail yet or when the source
// field value changes.
return !$this->get('thumbnail')->entity || $is_new || $this->hasSourceFieldChanged();
}
/**
* {@inheritdoc}
*/
public function preSave(EntityStorageInterface $storage) {
parent::preSave($storage);
$media_source = $this->getSource();
foreach ($this->translations as $langcode => $data) {
if ($this->hasTranslation($langcode)) {
$translation = $this->getTranslation($langcode);
// Try to set fields provided by the media source and mapped in
// media type config.
foreach ($translation->bundle->entity->getFieldMap() as $metadata_attribute_name => $entity_field_name) {
// Only save value in entity field if empty. Do not overwrite existing
// data.
if ($translation->hasField($entity_field_name) && ($translation->get($entity_field_name)->isEmpty() || $translation->hasSourceFieldChanged())) {
$translation->set($entity_field_name, $media_source->getMetadata($translation, $metadata_attribute_name));
}
}
// Try to set a default name for this media item if no name is provided.
if ($translation->get('name')->isEmpty()) {
$translation->setName($translation->getName());
}
// Set thumbnail.
if ($translation->shouldUpdateThumbnail()) {
$translation->updateThumbnail();
}
}
}
}
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
$is_new = !$update;
foreach ($this->translations as $langcode => $data) {
if ($this->hasTranslation($langcode)) {
$translation = $this->getTranslation($langcode);
if ($translation->bundle->entity->thumbnailDownloadsAreQueued() && $translation->shouldUpdateThumbnail($is_new)) {
\Drupal::queue('media_entity_thumbnail')->createItem(['id' => $translation->id()]);
}
}
}
}
/**
* {@inheritdoc}
*/
public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
parent::preSaveRevision($storage, $record);
$is_new_revision = $this->isNewRevision();
if (!$is_new_revision && isset($this->original) && empty($record->revision_log_message)) {
// If we are updating an existing media item without adding a
// new revision, we need to make sure $entity->revision_log_message is
// reset whenever it is empty.
// Therefore, this code allows us to avoid clobbering an existing log
// entry with an empty one.
$record->revision_log_message = $this->original->revision_log_message->value;
}
if ($is_new_revision) {
$record->revision_created = self::getRequestTime();
}
}
/**
* {@inheritdoc}
*/
public function validate() {
$media_source = $this->getSource();
if ($media_source instanceof MediaSourceEntityConstraintsInterface) {
$entity_constraints = $media_source->getEntityConstraints();
$this->getTypedData()->getDataDefinition()->setConstraints($entity_constraints);
}
if ($media_source instanceof MediaSourceFieldConstraintsInterface) {
$source_field_name = $media_source->getConfiguration()['source_field'];
$source_field_constraints = $media_source->getSourceFieldConstraints();
$this->get($source_field_name)->getDataDefinition()->setConstraints($source_field_constraints);
}
return parent::validate();
}
/**
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = parent::baseFieldDefinitions($entity_type);
$fields['name'] = BaseFieldDefinition::create('string')
->setLabel(t('Name'))
->setRequired(TRUE)
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setDefaultValue('')
->setSetting('max_length', 255)
->setDisplayOptions('form', [
'type' => 'string_textfield',
'weight' => -5,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'string',
'weight' => -5,
]);
$fields['thumbnail'] = BaseFieldDefinition::create('image')
->setLabel(t('Thumbnail'))
->setDescription(t('The thumbnail of the media item.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDisplayOptions('view', [
'type' => 'image',
'weight' => 5,
'label' => 'hidden',
'settings' => [
'image_style' => 'thumbnail',
],
])
->setDisplayConfigurable('view', TRUE)
->setReadOnly(TRUE);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Authored by'))
->setDescription(t('The user ID of the author.'))
->setRevisionable(TRUE)
->setDefaultValueCallback(static::class . '::getCurrentUserId')
->setSetting('target_type', 'user')
->setTranslatable(TRUE)
->setDisplayOptions('form', [
'type' => 'entity_reference_autocomplete',
'weight' => 5,
'settings' => [
'match_operator' => 'CONTAINS',
'size' => '60',
'autocomplete_type' => 'tags',
'placeholder' => '',
],
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'author',
'weight' => 0,
])
->setDisplayConfigurable('view', TRUE);
$fields['status']
->setDisplayOptions('form', [
'type' => 'boolean_checkbox',
'settings' => [
'display_label' => TRUE,
],
'weight' => 100,
])
->setDisplayConfigurable('form', TRUE);
$fields['created'] = BaseFieldDefinition::create('created')
->setLabel(t('Authored on'))
->setDescription(t('The time the media item was created.'))
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setDefaultValueCallback(static::class . '::getRequestTime')
->setDisplayOptions('form', [
'type' => 'datetime_timestamp',
'weight' => 10,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'timestamp',
'weight' => 0,
])
->setDisplayConfigurable('view', TRUE);
$fields['changed'] = BaseFieldDefinition::create('changed')
->setLabel(t('Changed'))
->setDescription(t('The time the media item was last edited.'))
->setTranslatable(TRUE)
->setRevisionable(TRUE);
return $fields;
}
/**
* Default value callback for 'uid' base field definition.
*
* @see ::baseFieldDefinitions()
*
* @return int[]
* An array of default values.
*/
public static function getCurrentUserId() {
return [\Drupal::currentUser()->id()];
}
/**
* {@inheritdoc}
*/
public static function getRequestTime() {
return \Drupal::time()->getRequestTime();
}
}
+225
View File
@@ -0,0 +1,225 @@
<?php
namespace Drupal\media\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
use Drupal\media\MediaTypeInterface;
/**
* Defines the Media type configuration entity.
*
* @ConfigEntityType(
* id = "media_type",
* label = @Translation("Media type"),
* label_collection = @Translation("Media types"),
* label_singular = @Translation("media type"),
* label_plural = @Translation("media types"),
* label_count = @PluralTranslation(
* singular = "@count media type",
* plural = "@count media types"
* ),
* handlers = {
* "form" = {
* "add" = "Drupal\media\MediaTypeForm",
* "edit" = "Drupal\media\MediaTypeForm",
* "delete" = "Drupal\media\Form\MediaTypeDeleteConfirmForm"
* },
* "list_builder" = "Drupal\media\MediaTypeListBuilder",
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider",
* }
* },
* admin_permission = "administer media types",
* config_prefix = "type",
* bundle_of = "media",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "status" = "status",
* },
* config_export = {
* "id",
* "label",
* "description",
* "source",
* "queue_thumbnail_downloads",
* "new_revision",
* "source_configuration",
* "field_map",
* "status",
* },
* links = {
* "add-form" = "/admin/structure/media/add",
* "edit-form" = "/admin/structure/media/manage/{media_type}",
* "delete-form" = "/admin/structure/media/manage/{media_type}/delete",
* "collection" = "/admin/structure/media",
* },
* )
*/
class MediaType extends ConfigEntityBundleBase implements MediaTypeInterface, EntityWithPluginCollectionInterface {
/**
* The machine name of this media type.
*
* @var string
*/
protected $id;
/**
* The human-readable name of the media type.
*
* @var string
*/
protected $label;
/**
* A brief description of this media type.
*
* @var string
*/
protected $description;
/**
* The media source ID.
*
* @var string
*/
protected $source;
/**
* Whether media items should be published by default.
*
* @var bool
*/
protected $status = TRUE;
/**
* Whether thumbnail downloads are queued.
*
* @var bool
*/
protected $queue_thumbnail_downloads = FALSE;
/**
* Default value of the 'Create new revision' checkbox of this media type.
*
* @var bool
*/
protected $new_revision = FALSE;
/**
* The media source configuration.
*
* @var array
*/
protected $source_configuration = [];
/**
* Lazy collection for the media source.
*
* @var \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection
*/
protected $sourcePluginCollection;
/**
* Field map. Fields provided by type plugin to be stored as entity fields.
*
* @var array
*/
protected $field_map = [];
/**
* {@inheritdoc}
*/
public function getPluginCollections() {
return [
'source_configuration' => $this->sourcePluginCollection(),
];
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return $this->description;
}
/**
* {@inheritdoc}
*/
public function setDescription($description) {
return $this->set('description', $description);
}
/**
* {@inheritdoc}
*/
public function thumbnailDownloadsAreQueued() {
return $this->queue_thumbnail_downloads;
}
/**
* {@inheritdoc}
*/
public function setQueueThumbnailDownloadsStatus($queue_thumbnail_downloads) {
return $this->set('queue_thumbnail_downloads', $queue_thumbnail_downloads);
}
/**
* {@inheritdoc}
*/
public function getSource() {
return $this->sourcePluginCollection()->get($this->source);
}
/**
* Returns media source lazy plugin collection.
*
* @return \Drupal\Core\Plugin\DefaultSingleLazyPluginCollection|null
* The tag plugin collection or NULL if the plugin ID was not set yet.
*/
protected function sourcePluginCollection() {
if (!$this->sourcePluginCollection && $this->source) {
$this->sourcePluginCollection = new DefaultSingleLazyPluginCollection(\Drupal::service('plugin.manager.media.source'), $this->source, $this->source_configuration);
}
return $this->sourcePluginCollection;
}
/**
* {@inheritdoc}
*/
public function getStatus() {
return $this->status;
}
/**
* {@inheritdoc}
*/
public function shouldCreateNewRevision() {
return $this->new_revision;
}
/**
* {@inheritdoc}
*/
public function setNewRevision($new_revision) {
return $this->set('new_revision', $new_revision);
}
/**
* {@inheritdoc}
*/
public function getFieldMap() {
return $this->field_map;
}
/**
* {@inheritdoc}
*/
public function setFieldMap(array $map) {
return $this->set('field_map', $map);
}
}
@@ -0,0 +1,207 @@
<?php
namespace Drupal\media\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* Provides a confirmation form to delete multiple media items at once.
*/
class MediaDeleteMultipleConfirmForm extends ConfirmFormBase {
/**
* The array of media items to delete, indexed by ID and language.
*
* @var string[][]
*/
protected $mediaItems = [];
/**
* The tempstore factory.
*
* @var \Drupal\user\PrivateTempStoreFactory
*/
protected $tempStoreFactory;
/**
* The entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* Constructs a MediaDeleteMultipleConfirmForm form object.
*
* @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
* The tempstore factory.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $manager
* The entity type manager.
*/
public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $manager) {
$this->tempStoreFactory = $temp_store_factory;
$this->storage = $manager->getStorage('media');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('user.private_tempstore'),
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'media_multiple_delete_confirm';
}
/**
* {@inheritdoc}
*/
public function getQuestion() {
return $this->formatPlural(count($this->mediaItems), 'Are you sure you want to delete this item?', 'Are you sure you want to delete these items?');
}
/**
* {@inheritdoc}
*/
public function getCancelUrl() {
// @todo Change to media library when #2834729 is done.
// https://www.drupal.org/node/2834729.
return new Url('system.admin_content');
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Delete');
}
/**
* {@inheritdoc}
*
* @todo Change to trait or base class when #2843395 is done.
* @see https://www.drupal.org/node/2843395
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$this->mediaItems = $this->tempStoreFactory->get('media_multiple_delete_confirm')->get($this->currentUser()->id());
if (empty($this->mediaItems)) {
return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString());
}
/** @var \Drupal\media\MediaInterface[] $entities */
$entities = $this->storage->loadMultiple(array_keys($this->mediaItems));
$items = [];
foreach ($this->mediaItems as $id => $langcodes) {
foreach ($langcodes as $langcode) {
$entity = $entities[$id]->getTranslation($langcode);
$key = $id . ':' . $langcode;
$default_key = $id . ':' . $entity->getUntranslated()->language()->getId();
// If we have a translated entity we build a nested list of translations
// that will be deleted.
$languages = $entity->getTranslationLanguages();
if (count($languages) > 1 && $entity->isDefaultTranslation()) {
$names = [];
foreach ($languages as $translation_langcode => $language) {
$names[] = $language->getName();
unset($items[$id . ':' . $translation_langcode]);
}
$items[$default_key] = [
'label' => [
'#markup' => $this->t('@label (Original translation) - <em>The following translations will be deleted:</em>', ['@label' => $entity->label()]),
],
'deleted_translations' => [
'#theme' => 'item_list',
'#items' => $names,
],
];
}
elseif (!isset($items[$default_key])) {
$items[$key] = $entity->label();
}
}
}
$form['entities'] = [
'#theme' => 'item_list',
'#items' => $items,
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*
* @todo Change to trait or base class when #2843395 is done.
* @see https://www.drupal.org/node/2843395
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form_state->getValue('confirm') && !empty($this->mediaItems)) {
$total_count = 0;
$delete_entities = [];
/** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */
$delete_translations = [];
/** @var \Drupal\media\MediaInterface[] $entities */
$entities = $this->storage->loadMultiple(array_keys($this->mediaItems));
foreach ($this->mediaItems as $id => $langcodes) {
foreach ($langcodes as $langcode) {
$entity = $entities[$id]->getTranslation($langcode);
if ($entity->isDefaultTranslation()) {
$delete_entities[$id] = $entity;
unset($delete_translations[$id]);
$total_count += count($entity->getTranslationLanguages());
}
elseif (!isset($delete_entities[$id])) {
$delete_translations[$id][] = $entity;
}
}
}
if ($delete_entities) {
$this->storage->delete($delete_entities);
$this->logger('media')->notice('Deleted @count media items.', ['@count' => count($delete_entities)]);
}
if ($delete_translations) {
$count = 0;
foreach ($delete_translations as $id => $translations) {
$entity = $entities[$id]->getUntranslated();
foreach ($translations as $translation) {
$entity->removeTranslation($translation->language()->getId());
}
$entity->save();
$count += count($translations);
}
if ($count) {
$total_count += $count;
$this->logger('media')->notice('Deleted @count media translations.', ['@count' => $count]);
}
}
if ($total_count) {
drupal_set_message($this->formatPlural($total_count, 'Deleted 1 media item.', 'Deleted @count media items.'));
}
$this->tempStoreFactory->get('media_multiple_delete_confirm')->delete(\Drupal::currentUser()->id());
}
// @todo Change to media library when #2834729 is done.
// https://www.drupal.org/node/2834729.
$form_state->setRedirect('system.admin_content');
}
}
@@ -0,0 +1,68 @@
<?php
namespace Drupal\media\Form;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityDeleteForm;
use Drupal\Core\Form\FormStateInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form for media type deletion.
*/
class MediaTypeDeleteConfirmForm extends EntityDeleteForm {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new MediaTypeDeleteConfirm object.
*
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
*/
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$num_entities = $this->entityTypeManager->getStorage('media')->getQuery()
->condition('bundle', $this->entity->id())
->count()
->execute();
if ($num_entities) {
$form['#title'] = $this->getQuestion();
$form['description'] = [
'#type' => 'inline_template',
'#template' => '<p>{{ message }}</p>',
'#context' => [
'message' => $this->formatPlural($num_entities,
'%type is used by @count media item on your site. You can not remove this media type until you have removed all of the %type media items.',
'%type is used by @count media items on your site. You can not remove this media type until you have removed all of the %type media items.',
['%type' => $this->entity->label()]),
],
];
return $form;
}
return parent::buildForm($form, $form_state);
}
}
@@ -0,0 +1,64 @@
<?php
namespace Drupal\media;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines an access control handler for the media entity.
*/
class MediaAccessControlHandler extends EntityAccessControlHandler {
/**
* {@inheritdoc}
*/
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
if ($account->hasPermission('administer media')) {
return AccessResult::allowed()->cachePerPermissions();
}
$is_owner = ($account->id() && $account->id() === $entity->getOwnerId());
switch ($operation) {
case 'view':
$access_result = AccessResult::allowedIf($account->hasPermission('view media') && $entity->isPublished())
->cachePerPermissions()
->addCacheableDependency($entity);
if (!$access_result->isAllowed()) {
$access_result->setReason("The 'view media' permission is required and the media item must be published.");
}
return $access_result;
case 'update':
if ($account->hasPermission('update any media')) {
return AccessResult::allowed()->cachePerPermissions();
}
return AccessResult::allowedIf($account->hasPermission('update media') && $is_owner)
->cachePerPermissions()
->cachePerUser()
->addCacheableDependency($entity);
case 'delete':
if ($account->hasPermission('delete any media')) {
return AccessResult::allowed()->cachePerPermissions();
}
return AccessResult::allowedIf($account->hasPermission('delete media') && $is_owner)
->cachePerPermissions()
->cachePerUser()
->addCacheableDependency($entity);
default:
return AccessResult::neutral()->cachePerPermissions();
}
}
/**
* {@inheritdoc}
*/
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
return AccessResult::allowedIfHasPermissions($account, ['administer media', 'create media'], 'OR');
}
}
+77
View File
@@ -0,0 +1,77 @@
<?php
namespace Drupal\media;
use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
* Form controller for the media edit forms.
*/
class MediaForm extends ContentEntityForm {
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
/** @var \Drupal\media\MediaTypeInterface $media_type */
$media_type = $this->entity->bundle->entity;
if ($this->operation === 'edit') {
$form['#title'] = $this->t('Edit %type_label @label', [
'%type_label' => $media_type->label(),
'@label' => $this->entity->label(),
]);
}
// Media author information for administrators.
if (isset($form['uid']) || isset($form['created'])) {
$form['author'] = [
'#type' => 'details',
'#title' => $this->t('Authoring information'),
'#group' => 'advanced',
'#attributes' => [
'class' => ['media-form-author'],
],
'#weight' => 90,
'#optional' => TRUE,
];
}
if (isset($form['uid'])) {
$form['uid']['#group'] = 'author';
}
if (isset($form['created'])) {
$form['created']['#group'] = 'author';
}
$form['#attached']['library'][] = 'media/form';
return $form;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$saved = parent::save($form, $form_state);
$context = ['@type' => $this->entity->bundle(), '%label' => $this->entity->label()];
$logger = $this->logger('media');
$t_args = ['@type' => $this->entity->bundle->entity->label(), '%label' => $this->entity->label()];
if ($saved === SAVED_NEW) {
$logger->notice('@type: added %label.', $context);
drupal_set_message($this->t('@type %label has been created.', $t_args));
}
else {
$logger->notice('@type: updated %label.', $context);
drupal_set_message($this->t('@type %label has been updated.', $t_args));
}
$form_state->setRedirectUrl($this->entity->toUrl('canonical'));
return $saved;
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
namespace Drupal\media;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\RevisionLogInterface;
use Drupal\user\EntityOwnerInterface;
/**
* Provides an interface defining an entity for media items.
*/
interface MediaInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface, EntityOwnerInterface, EntityPublishedInterface {
/**
* Gets the media item name.
*
* @return string
* The name of the media item.
*/
public function getName();
/**
* Sets the media item name.
*
* @param string $name
* The name of the media item.
*
* @return $this
*/
public function setName($name);
/**
* Returns the media item creation timestamp.
*
* @todo Remove and use the new interface when #2833378 is done.
* @see https://www.drupal.org/node/2833378
*
* @return int
* Creation timestamp of the media item.
*/
public function getCreatedTime();
/**
* Sets the media item creation timestamp.
*
* @todo Remove and use the new interface when #2833378 is done.
* @see https://www.drupal.org/node/2833378
*
* @param int $timestamp
* The media creation timestamp.
*
* @return \Drupal\media\MediaInterface
* The called media item.
*/
public function setCreatedTime($timestamp);
/**
* Returns the media source.
*
* @return \Drupal\media\MediaSourceInterface
* The media source.
*/
public function getSource();
}
+320
View File
@@ -0,0 +1,320 @@
<?php
namespace Drupal\media;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Plugin\PluginBase;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base implementation of media source plugin.
*/
abstract class MediaSourceBase extends PluginBase implements MediaSourceInterface, ContainerFactoryPluginInterface {
/**
* Plugin label.
*
* @var string
*/
protected $label;
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The entity field manager service.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* The field type plugin manager service.
*
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
*/
protected $fieldTypeManager;
/**
* The config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Constructs a new class 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\EntityTypeManagerInterface $entity_type_manager
* Entity type manager service.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* Entity field manager service.
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
* The field type plugin manager service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager, ConfigFactoryInterface $config_factory) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->fieldTypeManager = $field_type_manager;
$this->configFactory = $config_factory;
// Add the default configuration of the media source to the plugin.
$this->setConfiguration($configuration);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('entity_field.manager'),
$container->get('plugin.manager.field.field_type'),
$container->get('config.factory')
);
}
/**
* {@inheritdoc}
*/
public function setConfiguration(array $configuration) {
$this->configuration = NestedArray::mergeDeep(
$this->defaultConfiguration(),
$configuration
);
}
/**
* {@inheritdoc}
*/
public function getConfiguration() {
return $this->configuration;
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return [
'source_field' => '',
];
}
/**
* {@inheritdoc}
*/
public function getMetadata(MediaInterface $media, $attribute_name) {
switch ($attribute_name) {
case 'default_name':
return 'media:' . $media->bundle() . ':' . $media->uuid();
case 'thumbnail_uri':
$default_thumbnail_filename = $this->pluginDefinition['default_thumbnail_filename'];
return $this->configFactory->get('media.settings')->get('icon_base_uri') . '/' . $default_thumbnail_filename;
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
return [];
}
/**
* Get the source field options for the media type form.
*
* This returns all fields related to media entities, filtered by the allowed
* field types in the media source annotation.
*
* @return string[]
* A list of source field options for the media type form.
*/
protected function getSourceFieldOptions() {
// If there are existing fields to choose from, allow the user to reuse one.
$options = [];
foreach ($this->entityFieldManager->getFieldStorageDefinitions('media') as $field_name => $field) {
$allowed_type = in_array($field->getType(), $this->pluginDefinition['allowed_field_types'], TRUE);
if ($allowed_type && !$field->isBaseField()) {
$options[$field_name] = $field->getLabel();
}
}
return $options;
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$options = $this->getSourceFieldOptions();
$form['source_field'] = [
'#type' => 'select',
'#title' => $this->t('Field with source information'),
'#default_value' => $this->configuration['source_field'],
'#empty_option' => $this->t('- Create -'),
'#options' => $options,
'#description' => $this->t('Select the field that will store essential information about the media item. If "Create" is selected a new field will be automatically created.'),
];
if (!$options && $form_state->get('operation') === 'add') {
$form['source_field']['#access'] = FALSE;
$field_definition = $this->fieldTypeManager->getDefinition(reset($this->pluginDefinition['allowed_field_types']));
$form['source_field_message'] = [
'#markup' => $this->t('%field_type field will be automatically created on this type to store the essential information about the media item.', [
'%field_type' => $field_definition['label'],
]),
];
}
elseif ($form_state->get('operation') === 'edit') {
$form['source_field']['#access'] = FALSE;
$fields = $this->entityFieldManager->getFieldDefinitions('media', $form_state->get('type')->id());
$form['source_field_message'] = [
'#markup' => $this->t('%field_name field is used to store the essential information about the media item.', [
'%field_name' => $fields[$this->configuration['source_field']]->getLabel(),
]),
];
}
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
foreach (array_intersect_key($form_state->getValues(), $this->configuration) as $config_key => $config_value) {
$this->configuration[$config_key] = $config_value;
}
// If no source field is explicitly set, create it now.
if (empty($this->configuration['source_field'])) {
$field_storage = $this->createSourceFieldStorage();
$field_storage->save();
$this->configuration['source_field'] = $field_storage->getName();
}
}
/**
* Creates the source field storage definition.
*
* By default, the first field type listed in the plugin definition's
* allowed_field_types array will be the generated field's type.
*
* @return \Drupal\field\FieldStorageConfigInterface
* The unsaved field storage definition.
*/
protected function createSourceFieldStorage() {
return $this->entityTypeManager
->getStorage('field_storage_config')
->create([
'entity_type' => 'media',
'field_name' => $this->getSourceFieldName(),
'type' => reset($this->pluginDefinition['allowed_field_types']),
]);
}
/**
* Returns the source field storage definition.
*
* @return \Drupal\Core\Field\FieldStorageDefinitionInterface|null
* The field storage definition or NULL if it doesn't exists.
*/
protected function getSourceFieldStorage() {
// Nothing to do if no source field is configured yet.
$field = $this->configuration['source_field'];
if ($field) {
// Even if we do know the name of the source field, there's no
// guarantee that it exists.
$fields = $this->entityFieldManager->getFieldStorageDefinitions('media');
return isset($fields[$field]) ? $fields[$field] : NULL;
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function getSourceFieldDefinition(MediaTypeInterface $type) {
// Nothing to do if no source field is configured yet.
$field = $this->configuration['source_field'];
if ($field) {
// Even if we do know the name of the source field, there is no
// guarantee that it already exists.
$fields = $this->entityFieldManager->getFieldDefinitions('media', $type->id());
return isset($fields[$field]) ? $fields[$field] : NULL;
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function createSourceField(MediaTypeInterface $type) {
$storage = $this->getSourceFieldStorage() ?: $this->createSourceFieldStorage();
return $this->entityTypeManager
->getStorage('field_config')
->create([
'field_storage' => $storage,
'bundle' => $type->id(),
'label' => $this->pluginDefinition['label'],
'required' => TRUE,
]);
}
/**
* Determine the name of the source field.
*
* @return string
* The source field name. If one is already stored in configuration, it is
* returned. Otherwise, a new, unused one is generated.
*/
protected function getSourceFieldName() {
$base_id = 'field_media_' . $this->getPluginId();
$tries = 0;
$storage = $this->entityTypeManager->getStorage('field_storage_config');
// Iterate at least once, until no field with the generated ID is found.
do {
$id = $base_id;
// If we've tried before, increment and append the suffix.
if ($tries) {
$id .= '_' . $tries;
}
$field = $storage->load('media.' . $id);
$tries++;
} while ($field);
return $id;
}
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\media;
/**
* Defines an interface for a media source with entity constraints.
*
* This allows a media source to optionally add entity validation constraints
* for media items. To add constraints at the source field level, a media source
* can also implement MediaSourceFieldConstraintsInterface.
*
* @see \Drupal\media\MediaSourceInterface
* @see \Drupal\media\MediaSourceFieldConstraintsInterface.php
* @see \Drupal\media\MediaSourceBase
* @see \Drupal\media\Entity\Media
*/
interface MediaSourceEntityConstraintsInterface extends MediaSourceInterface {
/**
* Gets media source-specific validation constraints for a media item.
*
* @return \Symfony\Component\Validator\Constraint[]
* An array of validation constraint definitions, keyed by constraint name.
* Each constraint definition can be used for instantiating
* \Symfony\Component\Validator\Constraint objects.
*/
public function getEntityConstraints();
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\media;
/**
* Defines an interface for a media source with source field constraints.
*
* This allows a media source to optionally add source field validation
* constraints for media items. To add constraints at the entity level, a
* media source can also implement MediaSourceEntityConstraintsInterface.
*
* @see \Drupal\media\MediaSourceInterface
* @see \Drupal\media\MediaSourceEntityConstraintsInterface
* @see \Drupal\media\MediaSourceBase
* @see \Drupal\media\Entity\Media
*/
interface MediaSourceFieldConstraintsInterface extends MediaSourceInterface {
/**
* Gets media source-specific validation constraints for a source field.
*
* @return \Symfony\Component\Validator\Constraint[]
* An array of validation constraint definitions, keyed by constraint name.
* Each constraint definition can be used for instantiating
* \Symfony\Component\Validator\Constraint objects.
*/
public function getSourceFieldConstraints();
}
@@ -0,0 +1,142 @@
<?php
namespace Drupal\media;
use Drupal\Component\Plugin\ConfigurablePluginInterface;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\Plugin\PluginFormInterface;
/**
* Defines the interface for media source plugins.
*
* Media sources provide the critical link between media items in Drupal and the
* actual media itself, which typically exists independently of Drupal. Each
* media source works with a certain kind of media. For example, local files and
* YouTube videos can both be catalogued in a similar way as media items, but
* they need very different handling to actually display them.
*
* Each media type needs exactly one source. A single source can be used on many
* media types.
*
* Examples of possible sources are:
* - File: handles local files,
* - Image: handles local images,
* - oEmbed: handles resources that are exposed through the oEmbed standard,
* - YouTube: handles YouTube videos,
* - SoundCould: handles SoundCloud audio,
* - Instagram: handles Instagram posts,
* - Twitter: handles tweets,
* - ...
*
* Their responsibilities are:
* - Defining how media is represented (stored). Media sources are not
* responsible for actually storing the media. They only define how it is
* represented on a media item (usually using some kind of a field).
* - Providing thumbnails. Media sources that are responsible for remote
* media will generally fetch the image from a third-party API and make
* it available for the local usage. Media sources that represent local
* media (such as images) will usually use some locally provided image.
* Media sources should fall back to a pre-defined default thumbnail if
* everything else fails.
* - Validating a media item before it is saved. The entity constraint system
* will be used to ensure the valid structure of the media item.
* For example, media sources that represent remote media might check the
* URL or other identifier, while sources that represent local files might
* check the MIME type of the file.
* - Providing a default name for a media item. This will save users from
* manually entering the name when it can be reliably set automatically.
* Media sources for local files will generally use the filename, while media
* sources for remote resources might obtain a title attribute through a
* third-party API. The name can always be overridden by the user.
* - Providing metadata specific to the given media type. For example, remote
* media sources generally get information available through a
* third-party API and make it available to Drupal, while local media sources
* can expose things such as EXIF or ID3.
* - Mapping metadata to the media item. Metadata that a media source exposes
* can automatically be mapped to the fields on the media item. Media
* sources will be able to define how this is done.
*
* @see \Drupal\media\Annotation\MediaSource
* @see \Drupal\media\MediaSourceBase
* @see \Drupal\media\MediaSourceManager
* @see \Drupal\media\MediaTypeInterface
* @see \Drupal\media\MediaSourceEntityConstraintsInterface
* @see \Drupal\media\MediaSourceFieldConstraintsInterface
* @see plugin_api
*/
interface MediaSourceInterface extends PluginInspectionInterface, ConfigurablePluginInterface, PluginFormInterface {
/**
* Default empty value for metadata fields.
*/
const METADATA_FIELD_EMPTY = '_none';
/**
* Gets a list of metadata attributes provided by this plugin.
*
* Most media sources have associated metadata, describing attributes
* such as:
* - dimensions
* - duration
* - encoding
* - date
* - location
* - permalink
* - licensing information
* - ...
*
* This method should list all metadata attributes that a media source MAY
* offer. In other words: it is possible that a particular media item does
* not contain a certain attribute. For example: an oEmbed media source can
* contain both video and images. Images don't have a duration, but
* videos do.
*
* (The term 'attributes' was chosen because it cannot be confused
* with 'fields' and 'properties', both of which are concepts in Drupal's
* Entity Field API.)
*
* @return array
* Associative array with:
* - keys: metadata attribute names
* - values: human-readable labels for those attribute names
*/
public function getMetadataAttributes();
/**
* Gets the value for a metadata attribute for a given media item.
*
* @param \Drupal\media\MediaInterface $media
* A media item.
* @param string $attribute_name
* Name of the attribute to fetch.
*
* @return mixed|null
* Metadata attribute value or NULL if unavailable.
*/
public function getMetadata(MediaInterface $media, $attribute_name);
/**
* Get the source field definition for a media type.
*
* @param \Drupal\media\MediaTypeInterface $type
* A media type.
*
* @return \Drupal\Core\Field\FieldDefinitionInterface|null
* The source field definition, or NULL if it doesn't exist or has not been
* configured yet.
*/
public function getSourceFieldDefinition(MediaTypeInterface $type);
/**
* Creates the source field definition for a type.
*
* @param \Drupal\media\MediaTypeInterface $type
* The media type.
*
* @return \Drupal\field\FieldConfigInterface
* The unsaved field definition. The field storage definition, if new,
* should also be unsaved.
*/
public function createSourceField(MediaTypeInterface $type);
}
@@ -0,0 +1,33 @@
<?php
namespace Drupal\media;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\media\Annotation\MediaSource;
/**
* Manages media source plugins.
*/
class MediaSourceManager extends DefaultPluginManager {
/**
* Constructs a new MediaSourceManager.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct('Plugin/media/Source', $namespaces, $module_handler, MediaSourceInterface::class, MediaSource::class);
$this->alterInfo('media_source_info');
$this->setCacheBackend($cache_backend, 'media_source_plugins');
}
}
+376
View File
@@ -0,0 +1,376 @@
<?php
namespace Drupal\media;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\media\Entity\MediaType;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for media type forms.
*/
class MediaTypeForm extends EntityForm {
/**
* Media source plugin manager.
*
* @var \Drupal\media\MediaSourceManager
*/
protected $sourceManager;
/**
* Entity field manager service.
*
* @var \Drupal\Core\Entity\EntityFieldManagerInterface
*/
protected $entityFieldManager;
/**
* Constructs a new class instance.
*
* @param \Drupal\media\MediaSourceManager $source_manager
* Media source plugin manager.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* Entity field manager service.
*/
public function __construct(MediaSourceManager $source_manager, EntityFieldManagerInterface $entity_field_manager) {
$this->sourceManager = $source_manager;
$this->entityFieldManager = $entity_field_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.media.source'),
$container->get('entity_field.manager')
);
}
/**
* Ajax callback triggered by the type provider select element.
*/
public function ajaxHandlerData(array $form, FormStateInterface $form_state) {
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand('#source-dependent', $form['source_dependent']));
return $response;
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
// Source is not set when the entity is initially created.
/** @var \Drupal\media\MediaSourceInterface $source */
$source = $this->entity->get('source') ? $this->entity->getSource() : NULL;
if ($this->operation === 'add') {
$form['#title'] = $this->t('Add media type');
}
$form['label'] = [
'#title' => $this->t('Name'),
'#type' => 'textfield',
'#default_value' => $this->entity->label(),
'#description' => $this->t('The human-readable name of this media type.'),
'#required' => TRUE,
'#size' => 30,
];
$form['id'] = [
'#type' => 'machine_name',
'#default_value' => $this->entity->id(),
'#maxlength' => 32,
'#disabled' => !$this->entity->isNew(),
'#machine_name' => [
'exists' => [MediaType::class, 'load'],
],
'#description' => $this->t('A unique machine-readable name for this media type.'),
];
$form['description'] = [
'#title' => $this->t('Description'),
'#type' => 'textarea',
'#default_value' => $this->entity->getDescription(),
'#description' => $this->t('Describe this media type. The text will be displayed on the <em>Add new media</em> page.'),
];
$plugins = $this->sourceManager->getDefinitions();
$options = [];
foreach ($plugins as $plugin_id => $definition) {
$options[$plugin_id] = $definition['label'];
}
$form['source_dependent'] = [
'#type' => 'container',
'#attributes' => ['id' => 'source-dependent'],
];
$form['source_dependent']['source'] = [
'#type' => 'select',
'#title' => $this->t('Media source'),
'#default_value' => $source ? $source->getPluginId() : NULL,
'#options' => $options,
'#description' => $this->t('Media source that is responsible for additional logic related to this media type.'),
'#ajax' => ['callback' => '::ajaxHandlerData'],
'#required' => TRUE,
];
if (!$source) {
$form['type']['#empty_option'] = $this->t('- Select media source -');
}
if ($source) {
// Media source plugin configuration.
$form['source_dependent']['source_configuration'] = [
'#type' => 'fieldset',
'#title' => $this->t('Media source configuration'),
'#tree' => TRUE,
];
$form['source_dependent']['source_configuration'] = $source->buildConfigurationForm($form['source_dependent']['source_configuration'], $this->getSourceSubFormState($form, $form_state));
}
// Field mapping configuration.
$form['source_dependent']['field_map'] = [
'#type' => 'fieldset',
'#title' => $this->t('Field mapping'),
'#tree' => TRUE,
'description' => [
'#markup' => '<p>' . $this->t('Media sources can provide metadata fields such as title, caption, size information, credits, etc. Media can automatically save this metadata information to entity fields, which can be configured below. Information will only be mapped if the entity field is empty.') . '</p>',
],
];
if (empty($source) || empty($source->getMetadataAttributes())) {
$form['source_dependent']['field_map']['#access'] = FALSE;
}
else {
$options = [MediaSourceInterface::METADATA_FIELD_EMPTY => $this->t('- Skip field -')];
foreach ($this->entityFieldManager->getFieldDefinitions('media', $this->entity->id()) as $field_name => $field) {
if (!($field instanceof BaseFieldDefinition) || $field_name === 'name') {
$options[$field_name] = $field->getLabel();
}
}
$field_map = $this->entity->getFieldMap();
foreach ($source->getMetadataAttributes() as $metadata_attribute_name => $metadata_attribute_label) {
$form['source_dependent']['field_map'][$metadata_attribute_name] = [
'#type' => 'select',
'#title' => $metadata_attribute_label,
'#options' => $options,
'#default_value' => isset($field_map[$metadata_attribute_name]) ? $field_map[$metadata_attribute_name] : MediaSourceInterface::METADATA_FIELD_EMPTY,
];
}
}
$form['additional_settings'] = [
'#type' => 'vertical_tabs',
'#attached' => [
'library' => ['media/type_form'],
],
];
$form['workflow'] = [
'#type' => 'details',
'#title' => $this->t('Publishing options'),
'#group' => 'additional_settings',
];
$form['workflow']['options'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Default options'),
'#default_value' => $this->getWorkflowOptions(),
'#options' => [
'status' => $this->t('Published'),
'new_revision' => $this->t('Create new revision'),
'queue_thumbnail_downloads' => $this->t('Queue thumbnail downloads'),
],
];
$form['workflow']['options']['status']['#description'] = $this->t('Media will be automatically published when created.');
$form['workflow']['options']['new_revision']['#description'] = $this->t('Automatically create new revisions. Users with the "Administer media" permission will be able to override this option.');
$form['workflow']['options']['queue_thumbnail_downloads']['#description'] = $this->t('Download thumbnails via a queue. When using remote media sources, the thumbnail generation could be a slow process. Using a queue allows for this process to be handled in the background.');
if ($this->moduleHandler->moduleExists('language')) {
$form['language'] = [
'#type' => 'details',
'#title' => $this->t('Language settings'),
'#group' => 'additional_settings',
];
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle('media', $this->entity->id());
$form['language']['language_configuration'] = [
'#type' => 'language_configuration',
'#entity_information' => [
'entity_type' => 'media',
'bundle' => $this->entity->id(),
],
'#default_value' => $language_configuration,
];
}
return $form;
}
/**
* Prepares workflow options to be used in the 'checkboxes' form element.
*
* @return array
* Array of options ready to be used in #options.
*/
protected function getWorkflowOptions() {
$workflow_options = [
'status' => $this->entity->getStatus(),
'new_revision' => $this->entity->shouldCreateNewRevision(),
'queue_thumbnail_downloads' => $this->entity->thumbnailDownloadsAreQueued(),
];
// Prepare workflow options to be used for 'checkboxes' form element.
$keys = array_keys(array_filter($workflow_options));
return array_combine($keys, $keys);
}
/**
* Gets subform state for the media source configuration subform.
*
* @param array $form
* Full form array.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* Parent form state.
*
* @return \Drupal\Core\Form\SubformStateInterface
* Sub-form state for the media source configuration form.
*/
protected function getSourceSubFormState(array $form, FormStateInterface $form_state) {
return SubformState::createForSubform($form['source_dependent']['source_configuration'], $form, $form_state)
->set('operation', $this->operation)
->set('type', $this->entity);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
if ($form['source_dependent']['source_configuration']) {
// Let the selected plugin validate its settings.
$this->entity->getSource()->validateConfigurationForm($form['source_dependent']['source_configuration'], $this->getSourceSubFormState($form, $form_state));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$form_state->setValue('field_map', array_filter(
$form_state->getValue('field_map', []),
function ($item) {
return $item != MediaSourceInterface::METADATA_FIELD_EMPTY;
}
));
parent::submitForm($form, $form_state);
$this->entity->setQueueThumbnailDownloadsStatus((bool) $form_state->getValue(['options', 'queue_thumbnail_downloads']))
->setStatus((bool) $form_state->getValue(['options', 'status']))
->setNewRevision((bool) $form_state->getValue(['options', 'new_revision']));
if ($form['source_dependent']['source_configuration']) {
// Let the selected plugin save its settings.
$this->entity->getSource()->submitConfigurationForm($form['source_dependent']['source_configuration'], $this->getSourceSubFormState($form, $form_state));
}
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$actions['submit']['#value'] = $this->t('Save');
$actions['delete']['#value'] = $this->t('Delete');
$actions['delete']['#access'] = $this->entity->access('delete');
return $actions;
}
/**
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$status = parent::save($form, $form_state);
/** @var \Drupal\media\MediaTypeInterface $media_type */
$media_type = $this->entity;
// If the media source is using a source field, ensure it's
// properly created.
$source = $media_type->getSource();
$source_field = $source->getSourceFieldDefinition($media_type);
if (!$source_field) {
$source_field = $source->createSourceField($media_type);
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
$storage = $source_field->getFieldStorageDefinition();
if ($storage->isNew()) {
$storage->save();
}
$source_field->save();
// Add the new field to the default form and view displays for this
// media type.
$field_name = $source_field->getName();
$field_type = $source_field->getType();
if ($source_field->isDisplayConfigurable('form')) {
// Use the default widget and settings.
$component = \Drupal::service('plugin.manager.field.widget')
->prepareConfiguration($field_type, []);
// @todo Replace entity_get_form_display() when #2367933 is done.
// https://www.drupal.org/node/2872159.
entity_get_form_display('media', $media_type->id(), 'default')
->setComponent($field_name, $component)
->save();
}
if ($source_field->isDisplayConfigurable('view')) {
// Use the default formatter and settings.
$component = \Drupal::service('plugin.manager.field.formatter')
->prepareConfiguration($field_type, []);
// @todo Replace entity_get_display() when #2367933 is done.
// https://www.drupal.org/node/2872159.
entity_get_display('media', $media_type->id(), 'default')
->setComponent($field_name, $component)
->save();
}
}
$t_args = ['%name' => $media_type->label()];
if ($status === SAVED_UPDATED) {
drupal_set_message($this->t('The media type %name has been updated.', $t_args));
}
elseif ($status === SAVED_NEW) {
drupal_set_message($this->t('The media type %name has been added.', $t_args));
$this->logger('media')->notice('Added media type %name.', $t_args);
}
// Override the "status" base field default value, for this media type.
$fields = $this->entityFieldManager->getFieldDefinitions('media', $media_type->id());
/** @var \Drupal\media\MediaInterface $media */
$media = $this->entityTypeManager->getStorage('media')->create(['bundle' => $media_type->id()]);
$value = (bool) $form_state->getValue(['options', 'status']);
if ($media->status->value != $value) {
$fields['status']->getConfig($media_type->id())->setDefaultValue($value)->save();
}
$form_state->setRedirectUrl($media_type->toUrl('collection'));
}
}
@@ -0,0 +1,97 @@
<?php
namespace Drupal\media;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\Core\Entity\EntityDescriptionInterface;
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
/**
* Provides an interface defining a media type entity.
*
* Media types are bundles for media items. They are used to group media with
* the same semantics. Media types are not about where media comes from. They
* are about the semantics that media has in the context of a given Drupal site.
*
* Media sources, on the other hand, are aware where media comes from and know
* how to represent and handle it in Drupal's context. They are aware of the low
* level details, while the media types don't care about them at all. That said,
* media types can not exist without media sources.
*
* Consider the following examples:
* - oEmbed media source which can represent any oEmbed resource. Media types
* that could be used with this source are "Videos", "Charts", "Music", etc.
* All of them are retrieved using the same protocol, but they represent very
* different things.
* - Media sources that represent files could be used with media types like
* "Invoices", "Subtitles", "Meeting notes", etc. They are all files stored on
* some kind of storage, but their meaning and uses in a Drupal site are
* different.
*
* @see \Drupal\media\MediaSourceInterface
*/
interface MediaTypeInterface extends ConfigEntityInterface, EntityDescriptionInterface, RevisionableEntityBundleInterface {
/**
* Returns whether thumbnail downloads are queued.
*
* @return bool
* TRUE if thumbnails are queued for download later, FALSE if they should be
* downloaded now.
*/
public function thumbnailDownloadsAreQueued();
/**
* Sets a flag to indicate that thumbnails should be downloaded via a queue.
*
* @param bool $queue_thumbnail_downloads
* The queue downloads flag.
*
* @return $this
*/
public function setQueueThumbnailDownloadsStatus($queue_thumbnail_downloads);
/**
* Returns the media source plugin.
*
* @return \Drupal\media\MediaSourceInterface
* The media source.
*/
public function getSource();
/**
* Sets whether new revisions should be created by default.
*
* @param bool $new_revision
* TRUE if media items of this type should create new revisions by default.
*
* @return $this
*/
public function setNewRevision($new_revision);
/**
* Returns the metadata field map.
*
* Field mapping allows site builders to map media item-related metadata to
* entity fields. This information will be used when saving a given media item
* and if metadata values will be available they are going to be automatically
* copied to the corresponding entity fields.
*
* @return array
* Field mapping array provided by media source with metadata attribute
* names as keys and entity field names as values.
*/
public function getFieldMap();
/**
* Sets the metadata field map.
*
* @param array $map
* Field mapping array with metadata attribute names as keys and entity
* field names as values.
*
* @return $this
*/
public function setFieldMap(array $map);
}
@@ -0,0 +1,49 @@
<?php
namespace Drupal\media;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
/**
* Provides a listing of media types.
*/
class MediaTypeListBuilder extends ConfigEntityListBuilder {
/**
* {@inheritdoc}
*/
public function buildHeader() {
$header['title'] = $this->t('Name');
$header['description'] = [
'data' => $this->t('Description'),
'class' => [RESPONSIVE_PRIORITY_MEDIUM],
];
return $header + parent::buildHeader();
}
/**
* {@inheritdoc}
*/
public function buildRow(EntityInterface $entity) {
$row['title'] = [
'data' => $entity->label(),
'class' => ['menu-label'],
];
$row['description']['data'] = ['#markup' => $entity->getDescription()];
return $row + parent::buildRow($entity);
}
/**
* {@inheritdoc}
*/
public function render() {
$build = parent::render();
$build['table']['#empty'] = $this->t('No media types available. <a href=":url">Add media type</a>.', [
':url' => Url::fromRoute('entity.media_type.add_form')->toString(),
]);
return $build;
}
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace Drupal\media;
use Drupal\views\EntityViewsData;
/**
* Provides the Views data for the media entity type.
*/
class MediaViewsData extends EntityViewsData {
/**
* {@inheritdoc}
*/
public function getViewsData() {
$data = parent::getViewsData();
$data['media_field_data']['table']['wizard_id'] = 'media';
$data['media_field_revision']['table']['wizard_id'] = 'media_revision';
return $data;
}
}
@@ -0,0 +1,201 @@
<?php
namespace Drupal\media\Plugin\Field\FieldFormatter;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\image\ImageStyleStorageInterface;
use Drupal\image\Plugin\Field\FieldFormatter\ImageFormatter;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\Render\RendererInterface;
use Drupal\media\MediaInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
/**
* Plugin implementation of the 'media_thumbnail' formatter.
*
* @FieldFormatter(
* id = "media_thumbnail",
* label = @Translation("Thumbnail"),
* field_types = {
* "entity_reference"
* }
* )
*/
class MediaThumbnailFormatter extends ImageFormatter {
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs an MediaThumbnailFormatter object.
*
* @param string $plugin_id
* The plugin_id for the formatter.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the formatter is associated.
* @param array $settings
* The formatter settings.
* @param string $label
* The formatter label display setting.
* @param string $view_mode
* The view mode.
* @param array $third_party_settings
* Any third party settings settings.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\image\ImageStyleStorageInterface $image_style_storage
* The image style entity storage handler.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, ImageStyleStorageInterface $image_style_storage, RendererInterface $renderer) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings, $current_user, $image_style_storage);
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['label'],
$configuration['view_mode'],
$configuration['third_party_settings'],
$container->get('current_user'),
$container->get('entity_type.manager')->getStorage('image_style'),
$container->get('renderer')
);
}
/**
* {@inheritdoc}
*
* This has to be overridden because FileFormatterBase expects $item to be
* of type \Drupal\file\Plugin\Field\FieldType\FileItem and calls
* isDisplayed() which is not in FieldItemInterface.
*/
protected function needsEntityLoad(EntityReferenceItem $item) {
return !$item->hasNewEntity();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = parent::settingsForm($form, $form_state);
$link_types = [
'content' => $this->t('Content'),
'media' => $this->t('Media entity'),
];
$element['image_link']['#options'] = $link_types;
return $element;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = parent::settingsSummary();
$link_types = [
'content' => $this->t('Linked to content'),
'media' => $this->t('Linked to media item'),
];
// Display this setting only if image is linked.
$image_link_setting = $this->getSetting('image_link');
if (isset($link_types[$image_link_setting])) {
$summary[] = $link_types[$image_link_setting];
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = [];
$media_items = $this->getEntitiesToView($items, $langcode);
// Early opt-out if the field is empty.
if (empty($media_items)) {
return $elements;
}
$image_style_setting = $this->getSetting('image_style');
/** @var \Drupal\media\MediaInterface[] $media_items */
foreach ($media_items as $delta => $media) {
$elements[$delta] = [
'#theme' => 'image_formatter',
'#item' => $media->get('thumbnail')->first(),
'#item_attributes' => [],
'#image_style' => $this->getSetting('image_style'),
'#url' => $this->getMediaThumbnailUrl($media, $items->getEntity()),
];
// Add cacheability of each item in the field.
$this->renderer->addCacheableDependency($elements[$delta], $media);
}
// Add cacheability of the image style setting.
if ($this->getSetting('image_link') && ($image_style = $this->imageStyleStorage->load($image_style_setting))) {
$this->renderer->addCacheableDependency($elements, $image_style);
}
return $elements;
}
/**
* {@inheritdoc}
*/
public static function isApplicable(FieldDefinitionInterface $field_definition) {
// This formatter is only available for entity types that reference
// media items.
return ($field_definition->getFieldStorageDefinition()->getSetting('target_type') == 'media');
}
/**
* Get the URL for the media thumbnail.
*
* @param \Drupal\media\MediaInterface $media
* The media item.
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity that the field belongs to.
*
* @return \Drupal\Core\Url|null
* The URL object for the media item or null if we don't want to add
* a link.
*/
protected function getMediaThumbnailUrl(MediaInterface $media, EntityInterface $entity) {
$url = NULL;
$image_link_setting = $this->getSetting('image_link');
// Check if the formatter involves a link.
if ($image_link_setting == 'content') {
if (!$entity->isNew()) {
$url = $entity->toUrl();
}
}
elseif ($image_link_setting === 'media') {
$url = $media->toUrl();
}
return $url;
}
}
@@ -0,0 +1,68 @@
<?php
namespace Drupal\media\Plugin\QueueWorker;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Queue\QueueWorkerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Process a queue of media items to fetch their thumbnails.
*
* @QueueWorker(
* id = "media_entity_thumbnail",
* title = @Translation("Thumbnail downloader"),
* cron = {"time" = 60}
* )
*/
class ThumbnailDownloader extends QueueWorkerBase implements ContainerFactoryPluginInterface {
/**
* The entity type manager service.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* Constructs a new class 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\EntityTypeManagerInterface $entity_type_manager
* Entity type manager service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityTypeManager = $entity_type_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_type.manager')
);
}
/**
* {@inheritdoc}
*/
public function processItem($data) {
/** @var \Drupal\media\Entity\Media $media */
if ($media = $this->entityTypeManager->getStorage('media')->load($data['id'])) {
$media->updateQueuedThumbnail();
$media->save();
}
}
}
@@ -0,0 +1,95 @@
<?php
namespace Drupal\media\Plugin\media\Source;
use Drupal\file\FileInterface;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
use Drupal\media\MediaSourceBase;
/**
* File entity media source.
*
* @see \Drupal\file\FileInterface
*
* @MediaSource(
* id = "file",
* label = @Translation("File"),
* description = @Translation("Use local files for reusable media."),
* allowed_field_types = {"file"},
* default_thumbnail_filename = "generic.png"
* )
*/
class File extends MediaSourceBase {
/**
* {@inheritdoc}
*/
public function getMetadataAttributes() {
return [];
}
/**
* {@inheritdoc}
*/
public function getMetadata(MediaInterface $media, $attribute_name) {
/** @var \Drupal\file\FileInterface $file */
$file = $media->get($this->configuration['source_field'])->entity;
// If the source field is not required, it may be empty.
if (!$file) {
return parent::getMetadata($media, $attribute_name);
}
switch ($attribute_name) {
case 'default_name':
return $file->getFilename();
case 'thumbnail_uri':
return $this->getThumbnail($file) ?: parent::getMetadata($media, $attribute_name);
default:
return parent::getMetadata($media, $attribute_name);
}
}
/**
* Gets the thumbnail image URI based on a file entity.
*
* @param \Drupal\file\FileInterface $file
* A file entity.
*
* @return string
* File URI of the thumbnail image or NULL if there is no specific icon.
*/
protected function getThumbnail(FileInterface $file) {
$icon_base = $this->configFactory->get('media.settings')->get('icon_base_uri');
// We try to automatically use the most specific icon present in the
// $icon_base directory, based on the MIME type. For instance, if an
// icon file named "pdf.png" is present, it will be used if the file
// matches this MIME type.
$mimetype = $file->getMimeType();
$mimetype = explode('/', $mimetype);
$icon_names = [
$mimetype[0] . '--' . $mimetype[1],
$mimetype[1],
$mimetype[0],
];
foreach ($icon_names as $icon_name) {
$thumbnail = $icon_base . '/' . $icon_name . '.png';
if (is_file($thumbnail)) {
return $thumbnail;
}
}
return NULL;
}
/**
* {@inheritdoc}
*/
public function createSourceField(MediaTypeInterface $type) {
return parent::createSourceField($type)->set('settings', ['file_extensions' => 'txt doc docx pdf']);
}
}
@@ -0,0 +1,160 @@
<?php
namespace Drupal\media\Plugin\media\Source;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityFieldManagerInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldTypePluginManagerInterface;
use Drupal\Core\File\FileSystem;
use Drupal\Core\Image\ImageFactory;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Image entity media source.
*
* @see \Drupal\Core\Image\ImageInterface
*
* @MediaSource(
* id = "image",
* label = @Translation("Image"),
* description = @Translation("Use local images for reusable media."),
* allowed_field_types = {"image"},
* default_thumbnail_filename = "no-thumbnail.png"
* )
*/
class Image extends File {
/**
* Key for "image width" metadata attribute.
*
* @var string
*/
const METADATA_ATTRIBUTE_WIDTH = 'width';
/**
* Key for "image height" metadata attribute.
*
* @var string
*/
const METADATA_ATTRIBUTE_HEIGHT = 'height';
/**
* The image factory service.
*
* @var \Drupal\Core\Image\ImageFactory
*/
protected $imageFactory;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystem
*/
protected $fileSystem;
/**
* Constructs a new class 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\EntityTypeManagerInterface $entity_type_manager
* Entity type manager service.
* @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
* Entity field manager service.
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
* The field type plugin manager service.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Core\Image\ImageFactory $image_factory
* The image factory.
* @param \Drupal\Core\File\FileSystem $file_system
* The file system service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager, ConfigFactoryInterface $config_factory, ImageFactory $image_factory, FileSystem $file_system) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $entity_field_manager, $field_type_manager, $config_factory);
$this->imageFactory = $image_factory;
$this->fileSystem = $file_system;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('entity_field.manager'),
$container->get('plugin.manager.field.field_type'),
$container->get('config.factory'),
$container->get('image.factory'),
$container->get('file_system')
);
}
/**
* {@inheritdoc}
*/
public function getMetadataAttributes() {
$attributes = parent::getMetadataAttributes();
$attributes += [
static::METADATA_ATTRIBUTE_WIDTH => $this->t('Width'),
static::METADATA_ATTRIBUTE_HEIGHT => $this->t('Height'),
];
return $attributes;
}
/**
* {@inheritdoc}
*/
public function getMetadata(MediaInterface $media, $name) {
// Get the file and image data.
/** @var \Drupal\file\FileInterface $file */
$file = $media->get($this->configuration['source_field'])->entity;
// If the source field is not required, it may be empty.
if (!$file) {
return parent::getMetadata($media, $name);
}
$uri = $file->getFileUri();
$image = $this->imageFactory->get($uri);
switch ($name) {
case static::METADATA_ATTRIBUTE_WIDTH:
return $image->getWidth() ?: NULL;
case static::METADATA_ATTRIBUTE_HEIGHT:
return $image->getHeight() ?: NULL;
case 'thumbnail_uri':
return $uri;
}
return parent::getMetadata($media, $name);
}
/**
* {@inheritdoc}
*/
public function createSourceField(MediaTypeInterface $type) {
/** @var \Drupal\field\FieldConfigInterface $field */
$field = parent::createSourceField($type);
// Reset the field to its default settings so that we don't inherit the
// settings from the parent class' source field.
$settings = $this->fieldTypeManager->getDefaultFieldSettings($field->getType());
return $field->set('settings', $settings);
}
}
@@ -0,0 +1,87 @@
<?php
namespace Drupal\media\Plugin\views\wizard;
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
/**
* Provides Views creation wizard for Media.
*
* @ViewsWizard(
* id = "media",
* base_table = "media_field_data",
* title = @Translation("Media")
* )
*/
class Media extends WizardPluginBase {
/**
* Set the created column.
*
* @var string
*/
protected $createdColumn = 'media_field_data-created';
/**
* Set default values for the filters.
*
* @var array
*/
protected $filters = [
'status' => [
'value' => '1',
'table' => 'media_field_data',
'field' => 'status',
'plugin_id' => 'boolean',
'entity_type' => 'media',
'entity_field' => 'status',
],
];
/**
* {@inheritdoc}
*/
public function getAvailableSorts() {
return [
'media_field_data-name:DESC' => $this->t('Media name'),
];
}
/**
* {@inheritdoc}
*/
protected function defaultDisplayOptions() {
$display_options = parent::defaultDisplayOptions();
// Add permission-based access control.
$display_options['access']['type'] = 'perm';
$display_options['access']['options']['perm'] = 'view media';
// Remove the default fields, since we are customizing them here.
unset($display_options['fields']);
// Add the name field, so that the display has content if the user switches
// to a row style that uses fields.
$display_options['fields']['name']['id'] = 'name';
$display_options['fields']['name']['table'] = 'media_field_data';
$display_options['fields']['name']['field'] = 'name';
$display_options['fields']['name']['entity_type'] = 'media';
$display_options['fields']['name']['entity_field'] = 'media';
$display_options['fields']['name']['label'] = '';
$display_options['fields']['name']['alter']['alter_text'] = 0;
$display_options['fields']['name']['alter']['make_link'] = 0;
$display_options['fields']['name']['alter']['absolute'] = 0;
$display_options['fields']['name']['alter']['trim'] = 0;
$display_options['fields']['name']['alter']['word_boundary'] = 0;
$display_options['fields']['name']['alter']['ellipsis'] = 0;
$display_options['fields']['name']['alter']['strip_tags'] = 0;
$display_options['fields']['name']['alter']['html'] = 0;
$display_options['fields']['name']['hide_empty'] = 0;
$display_options['fields']['name']['empty_zero'] = 0;
$display_options['fields']['name']['settings']['link_to_entity'] = 1;
$display_options['fields']['name']['plugin_id'] = 'field';
return $display_options;
}
}
@@ -0,0 +1,99 @@
<?php
namespace Drupal\media\Plugin\views\wizard;
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
/**
* Provides Views creation wizard for Media revisions.
*
* @ViewsWizard(
* id = "media_revision",
* base_table = "media_field_revision",
* title = @Translation("Media revisions")
* )
*/
class MediaRevision extends WizardPluginBase {
/**
* Set the created column.
*
* @var string
*/
protected $createdColumn = 'media_field_revision-created';
/**
* Set default values for the filters.
*
* @var array
*/
protected $filters = [
'status' => [
'value' => '1',
'table' => 'media_field_revision',
'field' => 'status',
'plugin_id' => 'boolean',
'entity_type' => 'media',
'entity_field' => 'status',
],
];
/**
* {@inheritdoc}
*/
protected function defaultDisplayOptions() {
$display_options = parent::defaultDisplayOptions();
// Add permission-based access control.
$display_options['access']['type'] = 'perm';
$display_options['access']['options']['perm'] = 'view all revisions';
// Remove the default fields, since we are customizing them here.
unset($display_options['fields']);
// Add the changed field.
$display_options['fields']['changed']['id'] = 'changed';
$display_options['fields']['changed']['table'] = 'media_field_revision';
$display_options['fields']['changed']['field'] = 'changed';
$display_options['fields']['changed']['entity_type'] = 'media';
$display_options['fields']['changed']['entity_field'] = 'changed';
$display_options['fields']['changed']['alter']['alter_text'] = FALSE;
$display_options['fields']['changed']['alter']['make_link'] = FALSE;
$display_options['fields']['changed']['alter']['absolute'] = FALSE;
$display_options['fields']['changed']['alter']['trim'] = FALSE;
$display_options['fields']['changed']['alter']['word_boundary'] = FALSE;
$display_options['fields']['changed']['alter']['ellipsis'] = FALSE;
$display_options['fields']['changed']['alter']['strip_tags'] = FALSE;
$display_options['fields']['changed']['alter']['html'] = FALSE;
$display_options['fields']['changed']['hide_empty'] = FALSE;
$display_options['fields']['changed']['empty_zero'] = FALSE;
$display_options['fields']['changed']['plugin_id'] = 'field';
$display_options['fields']['changed']['type'] = 'timestamp';
$display_options['fields']['changed']['settings']['date_format'] = 'medium';
$display_options['fields']['changed']['settings']['custom_date_format'] = '';
$display_options['fields']['changed']['settings']['timezone'] = '';
// Add the name field.
$display_options['fields']['name']['id'] = 'name';
$display_options['fields']['name']['table'] = 'media_field_revision';
$display_options['fields']['name']['field'] = 'name';
$display_options['fields']['name']['entity_type'] = 'media';
$display_options['fields']['name']['entity_field'] = 'name';
$display_options['fields']['name']['label'] = '';
$display_options['fields']['name']['alter']['alter_text'] = 0;
$display_options['fields']['name']['alter']['make_link'] = 0;
$display_options['fields']['name']['alter']['absolute'] = 0;
$display_options['fields']['name']['alter']['trim'] = 0;
$display_options['fields']['name']['alter']['word_boundary'] = 0;
$display_options['fields']['name']['alter']['ellipsis'] = 0;
$display_options['fields']['name']['alter']['strip_tags'] = 0;
$display_options['fields']['name']['alter']['html'] = 0;
$display_options['fields']['name']['hide_empty'] = 0;
$display_options['fields']['name']['empty_zero'] = 0;
$display_options['fields']['name']['settings']['link_to_entity'] = 0;
$display_options['fields']['name']['plugin_id'] = 'field';
return $display_options;
}
}
@@ -0,0 +1,48 @@
{#
/**
* @file
* Default theme implementation to present a media item.
*
* Available variables:
* - media: The media item, with limited access to object properties and
* methods. Only method names starting with "get", "has", or "is" and
* a few common methods such as "id", "label", and "bundle" are available.
* For example:
* - entity.getEntityTypeId() will return the entity type ID.
* - entity.hasField('field_example') returns TRUE if the entity includes
* field_example. (This does not indicate the presence of a value in this
* field.)
* Calling other methods, such as entity.delete(), will result in
* an exception.
* See \Drupal\Core\Entity\EntityInterface for a full list of methods.
* - name: Name of the media item.
* - content: Media content.
* - title_prefix: Additional output populated by modules, intended to be
* displayed in front of the main title tag that appears in the template.
* - title_suffix: Additional output populated by modules, intended to be
* displayed after the main title tag that appears in the template.
* - view_mode: View mode; for example, "teaser" or "full".
* - attributes: HTML attributes for the containing element.
* - title_attributes: Same as attributes, except applied to the main title
* tag that appears in the template.
*
* @see template_preprocess_media()
*
* @ingroup themeable
*/
#}
<div{{ attributes }}>
{#
In the 'full' view mode the entity label is assumed to be displayed as the
page title, so we do not display it here.
#}
{{ title_prefix }}
{% if label and view_mode != 'full' %}
<h2{{ title_attributes }}>
{{ label }}
</h2>
{% endif %}
{{ title_suffix }}
{{ content }}
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

@@ -0,0 +1,15 @@
media.source.test:
type: media.source.field_aware
label: 'Test media source configuration'
mapping:
test_config_value:
type: string
label: 'Test config value'
media.source.test_translation:
type: media.source.test
label: 'Test media source with translations'
media.source.test_constraints:
type: media.source.test
label: 'Test media source with constraints configuration'
@@ -0,0 +1,12 @@
name: 'Test media source'
type: module
description: 'Provides test media source to test configuration forms.'
# core: 8.x
package: Testing
# version: VERSION
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
@@ -0,0 +1,25 @@
<?php
namespace Drupal\media_test_source\Plugin\Validation\Constraint;
use Symfony\Component\Validator\Constraint;
/**
* A media test constraint.
*
* @Constraint(
* id = "MediaTestConstraint",
* label = @Translation("Media constraint for test purposes.", context = "Validation"),
* type = { "entity", "string" }
* )
*/
class MediaTestConstraint extends Constraint {
/**
* The default violation message.
*
* @var string
*/
public $message = 'Inappropriate text.';
}
@@ -0,0 +1,34 @@
<?php
namespace Drupal\media_test_source\Plugin\Validation\Constraint;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* Validates the MediaTestConstraint.
*/
class MediaTestConstraintValidator extends ConstraintValidator {
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
if ($value instanceof EntityInterface) {
$string_to_test = $value->label();
}
elseif ($value instanceof FieldItemListInterface) {
$string_to_test = $value->value;
}
else {
return;
}
if (strpos($string_to_test, 'love Drupal') === FALSE) {
$this->context->addViolation($constraint->message);
}
}
}
@@ -0,0 +1,88 @@
<?php
namespace Drupal\media_test_source\Plugin\media\Source;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\media\MediaInterface;
use Drupal\media\MediaSourceBase;
/**
* Provides test media source.
*
* @MediaSource(
* id = "test",
* label = @Translation("Test source"),
* description = @Translation("Test media source."),
* allowed_field_types = {"string"},
* )
*/
class Test extends MediaSourceBase {
/**
* {@inheritdoc}
*/
public function getMetadataAttributes() {
// The metadata attributes are kept in state storage. This allows tests to
// change the metadata attributes and makes it easier to test different
// variations.
$attributes = \Drupal::state()->get('media_source_test_attributes', [
'attribute_1' => ['label' => $this->t('Attribute 1'), 'value' => 'Value 1'],
'attribute_2' => ['label' => $this->t('Attribute 2'), 'value' => 'Value 1'],
]);
return array_map(function ($item) {
return $item['label'];
}, $attributes);
}
/**
* {@inheritdoc}
*/
public function getMetadata(MediaInterface $media, $attribute_name) {
$attributes = \Drupal::state()->get('media_source_test_attributes', [
'attribute_1' => ['label' => $this->t('Attribute 1'), 'value' => 'Value 1'],
'attribute_2' => ['label' => $this->t('Attribute 2'), 'value' => 'Value 1'],
]);
if (in_array($attribute_name, array_keys($attributes))) {
return $attributes[$attribute_name]['value'];
}
return parent::getMetadata($media, $attribute_name);
}
/**
* {@inheritdoc}
*/
public function getPluginDefinition() {
return NestedArray::mergeDeep(
parent::getPluginDefinition(),
\Drupal::state()->get('media_source_test_definition', [])
);
}
/**
* {@inheritdoc}
*/
public function defaultConfiguration() {
return parent::defaultConfiguration() + [
'test_config_value' => 'This is default value.',
];
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = parent::buildConfigurationForm($form, $form_state);
$form['test_config_value'] = [
'#type' => 'textfield',
'#title' => $this->t('Test config value'),
'#default_value' => $this->configuration['test_config_value'],
];
return $form;
}
}
@@ -0,0 +1,36 @@
<?php
namespace Drupal\media_test_source\Plugin\media\Source;
use Drupal\media\MediaInterface;
/**
* Provides test media source.
*
* @MediaSource(
* id = "test_translation",
* label = @Translation("Test source with translations"),
* description = @Translation("Test media source with translations."),
* allowed_field_types = {"string"},
* thumbnail_alt_metadata_attribute = "test_thumbnail_alt",
* )
*/
class TestTranslation extends Test {
/**
* {@inheritdoc}
*/
public function getMetadata(MediaInterface $media, $attribute_name) {
if ($attribute_name == 'thumbnail_uri') {
return 'public://' . $media->language()->getId() . '.png';
}
if ($attribute_name == 'test_thumbnail_alt') {
$langcode = $media->language()->getId();
return $this->t('Test Thumbnail @language', ['@language' => $langcode], ['langcode' => $langcode]);
}
return parent::getMetadata($media, $attribute_name);
}
}
@@ -0,0 +1,34 @@
<?php
namespace Drupal\media_test_source\Plugin\media\Source;
use Drupal\media\MediaSourceEntityConstraintsInterface;
use Drupal\media\MediaSourceFieldConstraintsInterface;
/**
* Provides generic media type.
*
* @MediaSource(
* id = "test_constraints",
* label = @Translation("Test source with constraints"),
* description = @Translation("Test media source that provides constraints."),
* allowed_field_types = {"string_long"},
* )
*/
class TestWithConstraints extends Test implements MediaSourceEntityConstraintsInterface, MediaSourceFieldConstraintsInterface {
/**
* {@inheritdoc}
*/
public function getEntityConstraints() {
return \Drupal::state()->get('media_source_test_entity_constraints', []);
}
/**
* {@inheritdoc}
*/
public function getSourceFieldConstraints() {
return \Drupal::state()->get('media_source_test_field_constraints', []);
}
}
@@ -0,0 +1,11 @@
id: test
label: 'Test type'
description: 'Test type.'
source: test
source_configuration:
test_config_value: 'Kakec'
status: true
langcode: en
dependencies: { }
field_map:
metadata_attribute: 'field_attribute_config_test'
@@ -0,0 +1,14 @@
name: 'Media test type'
type: module
description: 'Provides test type for a media item.'
# core: 8.x
package: Testing
# version: VERSION
dependencies:
- media_test_source
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
@@ -0,0 +1,15 @@
name: 'Media test views'
type: module
description: 'Provides default views for views media tests.'
package: Testing
# version: VERSION
# core: 8.x
dependencies:
- media
- views
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
@@ -0,0 +1,134 @@
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\Media;
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Basic access tests for Media.
*
* @group media
*/
class MediaAccessTest extends MediaUiFunctionalTest {
use AssertPageCacheContextsAndTagsTrait;
/**
* Test some access control functionality.
*/
public function testMediaAccess() {
$assert_session = $this->assertSession();
$media_type = $this->createMediaType();
// Create media.
$media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
]);
$media->save();
$user_media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
'uid' => $this->nonAdminUser->id(),
]);
$user_media->save();
// We are logged in as admin, so test 'administer media' permission.
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/edit');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/delete');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalLogin($this->nonAdminUser);
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
// Test 'view media' permission.
user_role_revoke_permissions($role->id(), ['view media']);
$this->drupalGet('media/' . $media->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$access_result = $media->access('view', NULL, TRUE);
$this->assertSame("The 'view media' permission is required and the media item must be published.", $access_result->getReason());
$this->grantPermissions($role, ['view media']);
$this->drupalGet('media/' . $media->id());
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
// Test 'create media' permission.
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$this->grantPermissions($role, ['create media']);
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
// Test 'update media' and 'delete media' permissions.
$this->drupalGet('media/' . $user_media->id() . '/edit');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $user_media->id() . '/delete');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(403);
$this->grantPermissions($role, ['update media']);
$this->grantPermissions($role, ['delete media']);
$this->drupalGet('media/' . $user_media->id() . '/edit');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/delete');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
// Test 'update any media' and 'delete any media' permissions.
$this->drupalGet('media/' . $media->id() . '/edit');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $media->id() . '/delete');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(403);
$this->grantPermissions($role, ['update any media']);
$this->grantPermissions($role, ['delete any media']);
$this->drupalGet('media/' . $media->id() . '/edit');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $media->id() . '/delete');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
// Test the 'access media overview' permission.
$this->grantPermissions($role, ['access content overview']);
$this->drupalGet('admin/content');
$assert_session->linkByHrefNotExists('/admin/content/media');
$this->assertCacheContext('user');
// Create a new role, which implicitly checks if the permission exists.
$mediaOverviewRole = $this->createRole(['access content overview', 'access media overview']);
$this->nonAdminUser->addRole($mediaOverviewRole);
$this->nonAdminUser->save();
$this->drupalGet('admin/content');
$assert_session->linkByHrefExists('/admin/content/media');
$this->clickLink('Media');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$assert_session->elementExists('css', '.view-media');
$assert_session->pageTextContains($this->loggedInUser->getDisplayName());
$assert_session->pageTextContains($this->nonAdminUser->getDisplayName());
$assert_session->linkByHrefExists('/media/' . $media->id());
$assert_session->linkByHrefExists('/media/' . $user_media->id());
}
}
@@ -0,0 +1,62 @@
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\media\Entity\Media;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the media entity's cache tags.
*
* @group media
*/
class MediaCacheTagsTest extends EntityWithUriCacheTagsTestBase {
use MediaFunctionalTestCreateMediaTypeTrait;
/**
* {@inheritdoc}
*/
public static $modules = [
'media',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a media type.
$mediaType = $this->createMediaType();
// Create a media item.
$media = Media::create([
'bundle' => $mediaType->id(),
'name' => 'Unnamed',
]);
$media->save();
return $media;
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntity(EntityInterface $media) {
return ['timezone'];
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $media) {
// Each media item must have an author and a thumbnail.
return [
'user:' . $media->getOwnerId(),
'config:image.style.thumbnail',
'file:' . $media->get('thumbnail')->entity->id(),
];
}
}
@@ -0,0 +1,29 @@
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Base class for Media functional tests.
*/
abstract class MediaFunctionalTestBase extends BrowserTestBase {
use MediaFunctionalTestTrait;
use MediaFunctionalTestCreateMediaTypeTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'system',
'node',
'field_ui',
'views_ui',
'media',
'media_test_source',
];
}
@@ -0,0 +1,67 @@
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\MediaType;
/**
* Trait with helpers for Media functional tests.
*/
trait MediaFunctionalTestCreateMediaTypeTrait {
/**
* Creates a media type.
*
* @param array $values
* The media type values.
* @param string $source
* (optional) The media source plugin that is responsible for additional
* logic related to this media type. Defaults to 'test'.
*
* @return \Drupal\media\MediaTypeInterface
* A newly created media type.
*/
protected function createMediaType(array $values = [], $source = 'test') {
if (empty($values['bundle'])) {
$id = strtolower($this->randomMachineName());
}
else {
$id = $values['bundle'];
}
$values += [
'id' => $id,
'label' => $id,
'source' => $source,
'source_configuration' => [],
'field_map' => [],
'new_revision' => FALSE,
];
$media_type = MediaType::create($values);
$status = $media_type->save();
// @todo Rename to assertSame() when #1945040 is done.
// @see https://www.drupal.org/node/1945040
$this->assertIdentical(SAVED_NEW, $status, 'Media type was created successfully.');
// Ensure that the source field exists.
$source = $media_type->getSource();
$source_field = $source->getSourceFieldDefinition($media_type);
if (!$source_field) {
$source_field = $source->createSourceField($media_type);
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
$storage = $source_field->getFieldStorageDefinition();
$storage->save();
$source_field->save();
$media_type
->set('source_configuration', [
'source_field' => $source_field->getName(),
])
->save();
}
return $media_type;
}
}
@@ -0,0 +1,74 @@
<?php
namespace Drupal\Tests\media\Functional;
/**
* Trait with helpers for Media functional tests.
*/
trait MediaFunctionalTestTrait {
/**
* Permissions for the admin user that will be logged-in for test.
*
* @var array
*/
protected static $adminUserPermissions = [
// Media entity permissions.
'administer media',
'administer media fields',
'administer media form display',
'administer media display',
'administer media types',
'view media',
'create media',
'update media',
'update any media',
'delete media',
'delete any media',
// Other permissions.
'administer views',
'access content overview',
'view all revisions',
'administer content types',
'administer node fields',
'administer node form display',
'bypass node access',
];
/**
* An admin test user account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $adminUser;
/**
* A non-admin test user account.
*
* @var \Drupal\user\UserInterface
*/
protected $nonAdminUser;
/**
* The storage service.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Have two users ready to be used in tests.
$this->adminUser = $this->drupalCreateUser(static::$adminUserPermissions);
$this->nonAdminUser = $this->drupalCreateUser([]);
// Start off logged in as admin.
$this->drupalLogin($this->adminUser);
$this->storage = $this->container->get('entity_type.manager')->getStorage('media');
}
}
@@ -0,0 +1,49 @@
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests media Install / Uninstall logic.
*
* @group media
*/
class MediaInstallTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['media'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer modules']));
}
/**
* Tests reinstalling after being uninstalled.
*/
public function testReinstallAfterUninstall() {
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
// Uninstall the media module.
$this->container->get('module_installer')->uninstall(['media'], FALSE);
// Install the media module again, through a test module that depends on it.
// Note: We use a test module because in 8.4 the media module is hidden.
// @todo Simplify this in https://www.drupal.org/node/2897028 once it's
// shown again.
$this->drupalGet('/admin/modules');
$page->checkField('modules[media_test_views][enable]');
$page->pressButton('Install');
$assert_session->pageTextContains('Some required modules must be enabled');
$page->pressButton('Continue');
$this->assertSession()->pageTextNotContains('could not be moved/copied because a file by that name already exists in the destination directory');
}
}
@@ -0,0 +1,199 @@
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\media\MediaInterface;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the revisionability of media entities.
*
* @group media
*/
class MediaRevisionTest extends MediaFunctionalTestBase {
/**
* Checks media revision operations.
*/
public function testRevisions() {
$assert = $this->assertSession();
/** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $media_storage */
$media_storage = $this->container->get('entity.manager')->getStorage('media');
// Create a media type and media item.
$media_type = $this->createMediaType();
$media = $media_storage->create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
]);
$media->save();
// You can't access the revision page when there is only 1 revision.
$this->drupalGet('media/' . $media->id() . '/revisions/' . $media->getRevisionId() . '/view');
$assert->statusCodeEquals(403);
// Create some revisions.
$media_revisions = [];
$media_revisions[] = clone $media;
$revision_count = 3;
for ($i = 0; $i < $revision_count; $i++) {
$media->revision_log = $this->randomMachineName(32);
$media = $this->createMediaRevision($media);
$media_revisions[] = clone $media;
}
// Get the last revision for simple checks.
/** @var \Drupal\media\MediaInterface $media */
$media = end($media_revisions);
// Test permissions.
$this->drupalLogin($this->nonAdminUser);
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
// Test 'view all media revisions' permission ('view media' permission is
// needed as well).
user_role_revoke_permissions($role->id(), ['view media', 'view all media revisions']);
$this->drupalGet('media/' . $media->id() . '/revisions/' . $media->getRevisionId() . '/view');
$assert->statusCodeEquals(403);
$this->grantPermissions($role, ['view media', 'view all media revisions']);
$this->drupalGet('media/' . $media->id() . '/revisions/' . $media->getRevisionId() . '/view');
$assert->statusCodeEquals(200);
// Confirm the revision page shows the correct title.
$assert->pageTextContains($media->getName());
// Confirm that the last revision is the default revision.
$this->assertTrue($media->isDefaultRevision(), 'Last revision is the default.');
}
/**
* Tests creating revisions of a File media item.
*/
public function testFileMediaRevision() {
$assert = $this->assertSession();
$uri = 'temporary://foo.txt';
file_put_contents($uri, $this->randomString(128));
// Create a media item.
$this->drupalGet('/media/add/file');
$page = $this->getSession()->getPage();
$page->fillField('Name', 'Foobar');
$page->attachFileToField('File', $this->container->get('file_system')->realpath($uri));
$page->pressButton('Save');
$assert->addressMatches('/^\/media\/[0-9]+$/');
// The media item was just created, so it should only have one revision.
$media = $this->container
->get('entity_type.manager')
->getStorage('media')
->load(1);
$this->assertRevisionCount($media, 1);
// If we edit the item, we should get a new revision.
$this->drupalGet('/media/1/edit');
$assert->checkboxChecked('Create new revision');
$page = $this->getSession()->getPage();
$page->fillField('Name', 'Foobaz');
$page->pressButton('Save');
$this->assertRevisionCount($media, 2);
// Confirm the correct revision title appears on "view revisions" page.
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged(1);
$this->drupalGet("media/" . $media->id() . "/revisions/" . $media->getRevisionId() . "/view");
$assert->pageTextContains('Foobaz');
}
/**
* Tests creating revisions of a Image media item.
*/
public function testImageMediaRevision() {
$assert = $this->assertSession();
/** @var \Drupal\field\FieldConfigInterface $field */
// Disable the alt text field, because this is not a JavaScript test and
// the alt text field will therefore not appear without a full page refresh.
$field = FieldConfig::load('media.image.field_media_image');
$settings = $field->getSettings();
$settings['alt_field'] = FALSE;
$settings['alt_field_required'] = FALSE;
$field->set('settings', $settings);
$field->save();
// Create a media item.
$this->drupalGet('/media/add/image');
$page = $this->getSession()->getPage();
$page->fillField('Name', 'Foobar');
$page->attachFileToField('Image', \Drupal::root() . '/core/modules/media/tests/fixtures/example_1.jpeg');
$page->pressButton('Save');
$assert->addressMatches('/^\/media\/[0-9]+$/');
// The media item was just created, so it should only have one revision.
$media = $this->container
->get('entity_type.manager')
->getStorage('media')
->load(1);
$this->assertRevisionCount($media, 1);
// If we edit the item, we should get a new revision.
$this->drupalGet('/media/1/edit');
$assert->checkboxChecked('Create new revision');
$page = $this->getSession()->getPage();
$page->fillField('Name', 'Foobaz');
$page->pressButton('Save');
$this->assertRevisionCount($media, 2);
// Confirm the correct revision title appears on "view revisions" page.
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged(1);
$this->drupalGet("media/" . $media->id() . "/revisions/" . $media->getRevisionId() . "/view");
$assert->pageTextContains('Foobaz');
}
/**
* Creates a new revision for a given media item.
*
* @param \Drupal\media\MediaInterface $media
* A media object.
*
* @return \Drupal\media\MediaInterface
* A media object with up to date revision information.
*/
protected function createMediaRevision(MediaInterface $media) {
$media->setName($this->randomMachineName());
$media->setNewRevision();
$media->save();
return $media;
}
/**
* Asserts that an entity has a certain number of revisions.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity in question.
* @param int $expected_revisions
* The expected number of revisions.
*/
protected function assertRevisionCount(EntityInterface $entity, $expected_revisions) {
$entity_type = $entity->getEntityType();
$count = $this->container
->get('entity.query')
->get($entity_type->id())
->count()
->allRevisions()
->condition($entity_type->getKey('id'), $entity->id())
->execute();
$this->assertSame($expected_revisions, (int) $count);
}
}
@@ -0,0 +1,65 @@
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\media\Entity\MediaType;
/**
* Tests the file media source.
*
* @group media
*/
class MediaSourceFileTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// We need to test without any default configuration in place.
// @TODO: Remove this as part of https://www.drupal.org/node/2883813.
MediaType::load('file')->delete();
}
/**
* Test that it's possible to change the allowed file extensions.
*/
public function testSourceFieldSettingsEditing() {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$media_type = $this->createMediaType([], 'file');
$media_type_id = $media_type->id();
$this->assertSame('txt doc docx pdf', FieldConfig::load("media.$media_type_id.field_media_file")->get('settings')['file_extensions']);
$this->drupalGet("admin/structure/media/manage/$media_type_id/fields/media.$media_type_id.field_media_file");
// File extension field exists.
$assert_session->fieldExists('Allowed file extensions');
// Add another extension.
$page->fillField('settings[file_extensions]', 'txt, doc, docx, pdf, odt');
$page->pressButton('Save settings');
$this->drupalGet("admin/structure/media/manage/$media_type_id/fields/media.$media_type_id.field_media_file");
// Verify that new extension is present.
$assert_session->fieldValueEquals('settings[file_extensions]', 'txt, doc, docx, pdf, odt');
$this->assertSame('txt doc docx pdf odt', FieldConfig::load("media.$media_type_id.field_media_file")->get('settings')['file_extensions']);
}
/**
* Ensure source field deletion is not possible.
*/
public function testPreventSourceFieldDeletion() {
$media_type = $this->createMediaType([], 'file');
$media_type_id = $media_type->id();
$this->drupalGet("admin/structure/media/manage/$media_type_id/fields/media.$media_type_id.field_media_file/delete");
$this->assertSession()->statusCodeEquals(403);
}
}
@@ -0,0 +1,46 @@
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\Media;
/**
* Tests media template suggestions.
*
* @group media
*/
class MediaTemplateSuggestionsTest extends MediaFunctionalTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['media'];
/**
* Tests template suggestions from media_theme_suggestions_media().
*/
public function testMediaThemeHookSuggestions() {
$media_type = $this->createMediaType([
'new_revision' => FALSE,
'queue_thumbnail_downloads' => FALSE,
]);
// Create media item to be rendered.
$media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
]);
$media->save();
$view_mode = 'full';
// Simulate theming of the media item.
$build = \Drupal::entityTypeManager()->getViewBuilder('media')->view($media, $view_mode);
$variables['elements'] = $build;
$suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_media', [$variables]);
$this->assertEquals($suggestions, ['media__full', 'media__' . $media_type->id(), 'media__' . $media_type->id() . '__full'], 'Found expected media suggestions.');
}
}
@@ -0,0 +1,182 @@
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
/**
* Ensures that media UI works correctly.
*
* @group media
*/
class MediaUiFunctionalTest extends MediaFunctionalTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
// We need to test without any default configuration in place.
// @TODO: Remove this when https://www.drupal.org/node/2883813 lands.
MediaType::load('file')->delete();
MediaType::load('image')->delete();
}
/**
* Tests the media actions (add/edit/delete).
*/
public function testMediaWithOnlyOneMediaType() {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$media_type = $this->createMediaType([
'new_revision' => FALSE,
'queue_thumbnail_downloads' => FALSE,
]);
$this->drupalGet('media/add');
$assert_session->statusCodeEquals(200);
$assert_session->addressEquals('media/add/' . $media_type->id());
$assert_session->elementNotExists('css', '#edit-revision');
// Tests media add form.
$media_name = $this->randomMachineName();
$page->fillField('name[0][value]', $media_name);
$revision_log_message = $this->randomString();
$page->fillField('revision_log_message[0][value]', $revision_log_message);
$page->pressButton('Save');
$media_id = $this->container->get('entity.query')->get('media')->execute();
$media_id = reset($media_id);
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertEquals($media->getRevisionLogMessage(), $revision_log_message);
$this->assertEquals($media->getName(), $media_name);
$assert_session->titleEquals($media_name . ' | Drupal');
// Tests media edit form.
$media_type->setNewRevision(FALSE);
$media_type->save();
$media_name2 = $this->randomMachineName();
$this->drupalGet('media/' . $media_id . '/edit');
$assert_session->checkboxNotChecked('edit-revision');
$media_name = $this->randomMachineName();
$page->fillField('name[0][value]', $media_name2);
$page->pressButton('Save');
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertEquals($media->getName(), $media_name2);
$assert_session->titleEquals($media_name2 . ' | Drupal');
// Test that there is no empty vertical tabs element, if the container is
// empty (see #2750697).
// Make the "Publisher ID" and "Created" fields hidden.
$this->drupalGet('/admin/structure/media/manage/' . $media_type->id() . '/form-display');
$page->selectFieldOption('fields[created][parent]', 'hidden');
$page->selectFieldOption('fields[uid][parent]', 'hidden');
$page->pressButton('Save');
// Assure we are testing with a user without permission to manage revisions.
$this->drupalLogin($this->nonAdminUser);
// Check the container is not present.
$this->drupalGet('media/' . $media_id . '/edit');
$assert_session->elementNotExists('css', 'input.vertical-tabs__active-tab');
// Continue testing as admin.
$this->drupalLogin($this->adminUser);
// Enable revisions by default.
$previous_revision_id = $media->getRevisionId();
$media_type->setNewRevision(TRUE);
$media_type->save();
$this->drupalGet('media/' . $media_id . '/edit');
$assert_session->checkboxChecked('edit-revision');
$page->fillField('name[0][value]', $media_name);
$page->fillField('revision_log_message[0][value]', $revision_log_message);
$page->pressButton('Save');
$assert_session->titleEquals($media_name . ' | Drupal');
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertEquals($media->getRevisionLogMessage(), $revision_log_message);
$this->assertNotEquals($previous_revision_id, $media->getRevisionId());
// Test the status checkbox.
$this->drupalGet('media/' . $media_id . '/edit');
$page->uncheckField('status[value]');
$page->pressButton('Save');
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertFalse($media->isPublished());
// Tests media delete form.
$this->drupalGet('media/' . $media_id . '/edit');
$page->clickLink('Delete');
$assert_session->pageTextContains('This action cannot be undone');
$page->pressButton('Delete');
$media_id = \Drupal::entityQuery('media')->execute();
$this->assertFalse($media_id);
}
/**
* Tests the "media/add" and "media/mid" pages.
*
* Tests if the "media/add" page gives you a selecting option if there are
* multiple media types available.
*/
public function testMediaWithMultipleMediaTypes() {
$assert_session = $this->assertSession();
// Tests and creates the first media type.
$first_media_type = $this->createMediaType(['description' => $this->randomMachineName(32)]);
// Test and create a second media type.
$second_media_type = $this->createMediaType(['description' => $this->randomMachineName(32)]);
// Test if media/add displays two media type options.
$this->drupalGet('media/add');
// Checks for the first media type.
$assert_session->pageTextContains($first_media_type->label());
$assert_session->pageTextContains($first_media_type->getDescription());
// Checks for the second media type.
$assert_session->pageTextContains($second_media_type->label());
$assert_session->pageTextContains($second_media_type->getDescription());
// Continue testing media type filter.
$first_media_item = Media::create(['bundle' => $first_media_type->id()]);
$first_media_item->save();
$second_media_item = Media::create(['bundle' => $second_media_type->id()]);
$second_media_item->save();
// Go to first media item.
$this->drupalGet('media/' . $first_media_item->id());
$assert_session->statusCodeEquals(200);
$assert_session->pageTextContains($first_media_item->getName());
// Go to second media item.
$this->drupalGet('media/' . $second_media_item->id());
$assert_session->statusCodeEquals(200);
$assert_session->pageTextContains($second_media_item->getName());
}
}
@@ -0,0 +1,46 @@
<?php
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
use Drupal\Tests\media\Functional\MediaFunctionalTestCreateMediaTypeTrait;
use Drupal\Tests\media\Functional\MediaFunctionalTestTrait;
/**
* Base class for Media functional JavaScript tests.
*/
abstract class MediaJavascriptTestBase extends JavascriptTestBase {
use MediaFunctionalTestTrait;
use MediaFunctionalTestCreateMediaTypeTrait;
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'system',
'node',
'field_ui',
'views_ui',
'media',
'media_test_source',
];
/**
* Waits and asserts that a given element is visible.
*
* @param string $selector
* The CSS selector.
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 1000.
* @param string $message
* (Optional) Message to pass to assertJsCondition().
*/
protected function waitUntilVisible($selector, $timeout = 1000, $message = '') {
$condition = "jQuery('" . $selector . ":visible').length > 0";
$this->assertJsCondition($condition, $timeout, $message);
}
}
@@ -0,0 +1,80 @@
<?php
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
/**
* Tests the file media source.
*
* @group media
*/
class MediaSourceFileTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// We need to test without any default configuration in place.
// @TODO: Remove this as part of https://www.drupal.org/node/2883813.
MediaType::load('file')->delete();
}
/**
* Tests the file media source.
*/
public function testMediaFileSource() {
$media_type_id = 'test_media_file_type';
$source_field_id = 'field_media_file';
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->doTestCreateMediaType($media_type_id, 'file');
// Hide the name field widget to test default name generation.
$this->hideMediaTypeFieldWidget('name', $media_type_id);
$test_filename = $this->randomMachineName() . '.txt';
$test_filepath = 'public://' . $test_filename;
file_put_contents($test_filepath, $this->randomMachineName());
// Create a media item.
$this->drupalGet("media/add/{$media_type_id}");
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$assert_session->addressEquals('media/1');
// Make sure the thumbnail is displayed.
$assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'generic.png');
// Make sure checkbox changes the visibility of log message field.
$this->drupalGet("media/1/edit");
$page->uncheckField('revision');
$assert_session->elementAttributeContains('css', '.field--name-revision-log-message', 'style', 'display: none');
$page->checkField('revision');
$assert_session->elementAttributeNotContains('css', '.field--name-revision-log-message', 'style', 'display');
// Load the media and check if the label was properly populated.
$media = Media::load(1);
$this->assertEquals($test_filename, $media->getName());
// Test the MIME type icon.
$icon_base = \Drupal::config('media.settings')->get('icon_base_uri');
file_unmanaged_copy($icon_base . '/generic.png', $icon_base . '/text--plain.png');
$this->drupalGet("media/add/{$media_type_id}");
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'text--plain.png');
}
}
@@ -0,0 +1,80 @@
<?php
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\Plugin\media\Source\Image;
/**
* Tests the image media source.
*
* @group media
*/
class MediaSourceImageTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// We need to test without any default configuration in place.
// @TODO: Remove this as part of https://www.drupal.org/node/2883813.
MediaType::load('file')->delete();
MediaType::load('image')->delete();
}
/**
* Tests the image media source.
*/
public function testMediaImageSource() {
$media_type_id = 'test_media_image_type';
$source_field_id = 'field_media_image';
$provided_fields = [
Image::METADATA_ATTRIBUTE_WIDTH,
Image::METADATA_ATTRIBUTE_HEIGHT,
];
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->doTestCreateMediaType($media_type_id, 'image', $provided_fields);
// Create custom fields for the media type to store metadata attributes.
$fields = [
'field_string_width' => 'string',
'field_string_height' => 'string',
];
$this->createMediaTypeFields($fields, $media_type_id);
// Hide the name field widget to test default name generation.
$this->hideMediaTypeFieldWidget('name', $media_type_id);
$this->drupalGet("admin/structure/media/manage/{$media_type_id}");
$page->selectFieldOption("field_map[" . Image::METADATA_ATTRIBUTE_WIDTH . "]", 'field_string_width');
$page->selectFieldOption("field_map[" . Image::METADATA_ATTRIBUTE_HEIGHT . "]", 'field_string_height');
$page->pressButton('Save');
// Create a media item.
$this->drupalGet("media/add/{$media_type_id}");
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::root() . '/core/modules/media/tests/fixtures/example_1.jpeg');
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->fillField("{$source_field_id}[0][alt]", 'Image Alt Text 1');
$page->pressButton('Save');
$assert_session->addressEquals('media/1');
// Make sure the thumbnail is displayed from uploaded image.
$assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'example_1.jpeg');
// Load the media and check that all fields are properly populated.
$media = Media::load(1);
$this->assertEquals('example_1.jpeg', $media->getName());
$this->assertEquals('200', $media->get('field_string_width')->value);
$this->assertEquals('89', $media->get('field_string_height')->value);
}
}
@@ -0,0 +1,138 @@
<?php
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media\Entity\MediaType;
/**
* Base class for media source tests.
*/
abstract class MediaSourceTestBase extends MediaJavascriptTestBase {
/**
* Creates storage and field instance, attached to a given media type.
*
* @param string $field_name
* The field name.
* @param string $field_type
* The field type.
* @param string $media_type_id
* The media type config entity ID.
*/
protected function createMediaTypeField($field_name, $field_type, $media_type_id) {
$storage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'media',
'type' => $field_type,
]);
$storage->save();
FieldConfig::create([
'field_storage' => $storage,
'bundle' => $media_type_id,
])->save();
// Make the field widget visible in the form display.
$component = \Drupal::service('plugin.manager.field.widget')
->prepareConfiguration($field_type, []);
// @todo Replace entity_get_form_display() when #2367933 is done.
// https://www.drupal.org/node/2872159.
$entity_form_display = entity_get_form_display('media', $media_type_id, 'default');
$entity_form_display->setComponent($field_name, $component)
->save();
// Use the default formatter and settings.
$component = \Drupal::service('plugin.manager.field.formatter')
->prepareConfiguration($field_type, []);
// @todo Replace entity_get_display() when #2367933 is done.
// https://www.drupal.org/node/2872159.
$entity_display = entity_get_display('media', $media_type_id, 'default');
$entity_display->setComponent($field_name, $component)
->save();
}
/**
* Create a set of fields in a media type.
*
* @param array $fields
* An associative array where keys are field names and values field types.
* @param string $media_type_id
* The media type config entity ID.
*/
protected function createMediaTypeFields(array $fields, $media_type_id) {
foreach ($fields as $field_name => $field_type) {
$this->createMediaTypeField($field_name, $field_type, $media_type_id);
}
}
/**
* Hides a widget in the default form display config.
*
* @param string $field_name
* The field name.
* @param string $media_type_id
* The media type config entity ID.
*/
protected function hideMediaTypeFieldWidget($field_name, $media_type_id) {
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity_form_display */
$entity_form_display = entity_get_form_display('media', $media_type_id, 'default');
if ($entity_form_display->getComponent($field_name)) {
$entity_form_display->removeComponent($field_name)->save();
}
}
/**
* Test generic media type creation.
*
* @param string $media_type_id
* The media type config entity ID.
* @param string $source_id
* The media source ID.
* @param array $provided_fields
* (optional) An array of field machine names this type provides.
*
* @return \Drupal\media\MediaTypeInterface
* The created media type.
*/
public function doTestCreateMediaType($media_type_id, $source_id, array $provided_fields = []) {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', $media_type_id);
$this->getSession()
->wait(5000, "jQuery('.machine-name-value').text() === '{$media_type_id}'");
// Make sure the source is available.
$assert_session->fieldExists('Media source');
$assert_session->optionExists('Media source', $source_id);
$page->selectFieldOption('Media source', $source_id);
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
// Make sure the provided fields are visible on the form.
foreach ($provided_fields as $provided_field) {
$result = $assert_session->waitForElementVisible('css', 'select[name="field_map[' . $provided_field . ']"]');
$this->assertNotEmpty($result);
}
// Save the form to create the type.
$page->pressButton('Save');
$assert_session->pageTextContains('The media type ' . $media_type_id . ' has been added.');
$this->drupalGet('admin/structure/media');
$assert_session->pageTextContains($media_type_id);
// Bundle definitions are statically cached in the context of the test, we
// need to make sure we have updated information before proceeding with the
// actions on the UI.
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
return MediaType::load($media_type_id);
}
}
@@ -0,0 +1,101 @@
<?php
namespace Drupal\Tests\media\FunctionalJavascript;
/**
* Tests the media type creation.
*
* @group media
*/
class MediaTypeCreationTest extends MediaJavascriptTestBase {
/**
* Tests the media type creation form.
*/
public function testMediaTypeCreationFormWithDefaultField() {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$label = 'Type with Default Field';
$mediaTypeMachineName = str_replace(' ', '_', strtolower($label));
$this->drupalGet('admin/structure/media/add');
// Fill in a label to the media type.
$page->fillField('label', $label);
// Wait for machine name generation. Default: waitUntilVisible(), does not
// work properly.
$session->wait(5000, "jQuery('.machine-name-value').text() === '{$mediaTypeMachineName}'");
// Select the media source used by our media type.
$assert_session->fieldExists('Media source');
$assert_session->optionExists('Media source', 'test');
$page->selectFieldOption('Media source', 'test');
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
$page->pressButton('Save');
// Check whether the source field was correctly created.
$this->drupalGet("admin/structure/media/manage/{$mediaTypeMachineName}/fields");
// Check 2nd column of first data row, to be machine name for field name.
$assert_session->elementContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[2]', 'field_media_test');
// Check 3rd column of first data row, to be correct field type.
$assert_session->elementTextContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[3]', 'Text (plain)');
// Check that the source field is correctly assigned to media type.
$this->drupalGet("admin/structure/media/manage/{$mediaTypeMachineName}");
$assert_session->pageTextContains('Test source field is used to store the essential information about the media item.');
}
/**
* Test creation of media type, reusing an existing source field.
*/
public function testMediaTypeCreationReuseSourceField() {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
// Create a new media type, which should create a new field we can reuse.
$this->drupalGet('/admin/structure/media/add');
$page->fillField('label', 'Pastafazoul');
$session->wait(5000, "jQuery('.machine-name-value').text() === 'pastafazoul'");
$page->selectFieldOption('Media source', 'test');
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$label = 'Type reusing Default Field';
$mediaTypeMachineName = str_replace(' ', '_', strtolower($label));
$this->drupalGet('admin/structure/media/add');
// Fill in a label to the media type.
$page->fillField('label', $label);
// Wait for machine name generation. Default: waitUntilVisible(), does not
// work properly.
$session->wait(5000, "jQuery('.machine-name-value').text() === '{$mediaTypeMachineName}'");
// Select the media source used by our media type.
$assert_session->fieldExists('Media source');
$assert_session->optionExists('Media source', 'test');
$page->selectFieldOption('Media source', 'test');
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
// Select the existing field for re-use.
$page->selectFieldOption('source_configuration[source_field]', 'field_media_test');
$page->pressButton('Save');
// Check that no new fields were created.
$this->drupalGet("admin/structure/media/manage/{$mediaTypeMachineName}/fields");
// The reused field should be present...
$assert_session->pageTextContains('field_media_test');
// ...not a new, unique one.
$assert_session->pageTextNotContains('field_media_test_1');
}
}
@@ -0,0 +1,202 @@
<?php
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\field\FieldConfigInterface;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaSourceInterface;
/**
* Ensures that media UI works correctly.
*
* @group media
*/
class MediaUiJavascriptTest extends MediaJavascriptTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = [
'block',
'media_test_source',
];
/**
* The test media type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testMediaType;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
// We need to test without any default configuration in place.
// @TODO: Remove this as part of https://www.drupal.org/node/2883813.
MediaType::load('file')->delete();
MediaType::load('image')->delete();
}
/**
* Tests a media type administration.
*/
public function testMediaTypes() {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->drupalGet('admin/structure/media');
$assert_session->pageTextContains('No media types available. Add media type.');
$assert_session->linkExists('Add media type');
// Test the creation of a media type using the UI.
$name = $this->randomMachineName();
$description = $this->randomMachineName();
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', $name);
$machine_name = strtolower($name);
$this->assertJsCondition("jQuery('.machine-name-value').html() == '$machine_name'");
$page->selectFieldOption('source', 'test');
$this->assertJsCondition("jQuery('.form-item-source-configuration-test-config-value').length > 0;");
$page->fillField('description', $description);
$page->pressButton('Save');
$assert_session->pageTextContains('The media type ' . $name . ' has been added.');
$this->drupalGet('admin/structure/media');
$assert_session->pageTextContains($name);
$assert_session->pageTextContains($description);
// We need to clear the statically cached field definitions to account for
// fields that have been created by API calls in this test, since they exist
// in a separate memory space from the web server.
$this->container->get('entity_field.manager')->clearCachedFieldDefinitions();
// Assert that the field and field storage were created.
$media_type = MediaType::load($machine_name);
$source = $media_type->getSource();
/** @var \Drupal\field\FieldConfigInterface $source_field */
$source_field = $source->getSourceFieldDefinition($media_type);
$this->assertInstanceOf(FieldConfigInterface::class, $source_field, 'Source field exists.');
$this->assertFalse($source_field->isNew(), 'Source field was saved.');
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
$storage = $source_field->getFieldStorageDefinition();
$this->assertFalse($storage->isNew(), 'Source field storage definition was saved.');
$this->assertFalse($storage->isLocked(), 'Source field storage definition was not locked.');
/** @var \Drupal\media\MediaTypeInterface $media_type_storage */
$media_type_storage = $this->container->get('entity_type.manager')->getStorage('media_type');
$this->testMediaType = $media_type_storage->load(strtolower($name));
// Check if all action links exist.
$assert_session->linkByHrefExists('admin/structure/media/add');
$assert_session->linkByHrefExists('admin/structure/media/manage/' . $this->testMediaType->id());
$assert_session->linkByHrefExists('admin/structure/media/manage/' . $this->testMediaType->id() . '/fields');
$assert_session->linkByHrefExists('admin/structure/media/manage/' . $this->testMediaType->id() . '/form-display');
$assert_session->linkByHrefExists('admin/structure/media/manage/' . $this->testMediaType->id() . '/display');
// Assert that fields have expected values before editing.
$page->clickLink('Edit');
$assert_session->fieldValueEquals('label', $name);
$assert_session->fieldValueEquals('description', $description);
$assert_session->fieldValueEquals('source', 'test');
$assert_session->fieldValueEquals('label', $name);
$assert_session->checkboxNotChecked('edit-options-new-revision');
$assert_session->checkboxChecked('edit-options-status');
$assert_session->checkboxNotChecked('edit-options-queue-thumbnail-downloads');
$assert_session->pageTextContains('Create new revision');
$assert_session->pageTextContains('Automatically create new revisions. Users with the "Administer media" permission will be able to override this option.');
$assert_session->pageTextContains('Download thumbnails via a queue.');
$assert_session->pageTextContains('Media will be automatically published when created.');
$assert_session->pageTextContains('Media sources can provide metadata fields such as title, caption, size information, credits, etc. Media can automatically save this metadata information to entity fields, which can be configured below. Information will only be mapped if the entity field is empty.');
// Try to change media type and check if new configuration sub-form appears.
$page->selectFieldOption('source', 'test');
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
$assert_session->fieldExists('Test config value');
$assert_session->fieldValueEquals('Test config value', 'This is default value.');
$assert_session->fieldExists('Attribute 1');
$assert_session->fieldExists('Attribute 2');
// Test if the edit machine name is not editable.
$assert_session->fieldDisabled('Machine-readable name');
// Edit and save media type form fields with new values.
$new_name = $this->randomMachineName();
$new_description = $this->randomMachineName();
$page->fillField('label', $new_name);
$page->fillField('description', $new_description);
$page->selectFieldOption('source', 'test');
$page->fillField('Test config value', 'This is new config value.');
$page->selectFieldOption('field_map[attribute_1]', 'name');
$page->checkField('options[new_revision]');
$page->uncheckField('options[status]');
$page->checkField('options[queue_thumbnail_downloads]');
$page->pressButton('Save');
$assert_session->pageTextContains("The media type $new_name has been updated.");
// Test if edit worked and if new field values have been saved as expected.
$this->drupalGet('admin/structure/media/manage/' . $this->testMediaType->id());
$assert_session->fieldValueEquals('label', $new_name);
$assert_session->fieldValueEquals('description', $new_description);
$assert_session->fieldValueEquals('source', 'test');
$assert_session->checkboxChecked('options[new_revision]');
$assert_session->checkboxNotChecked('options[status]');
$assert_session->checkboxChecked('options[queue_thumbnail_downloads]');
$assert_session->fieldValueEquals('Test config value', 'This is new config value.');
$assert_session->fieldValueEquals('Attribute 1', 'name');
$assert_session->fieldValueEquals('Attribute 2', MediaSourceInterface::METADATA_FIELD_EMPTY);
/** @var \Drupal\media\MediaTypeInterface $loaded_media_type */
$loaded_media_type = $this->container->get('entity_type.manager')
->getStorage('media_type')
->load($this->testMediaType->id());
$this->assertEquals($loaded_media_type->id(), $this->testMediaType->id());
$this->assertEquals($loaded_media_type->label(), $new_name);
$this->assertEquals($loaded_media_type->getDescription(), $new_description);
$this->assertEquals($loaded_media_type->getSource()->getPluginId(), 'test');
$this->assertEquals($loaded_media_type->getSource()->getConfiguration()['test_config_value'], 'This is new config value.');
$this->assertTrue($loaded_media_type->shouldCreateNewRevision());
$this->assertTrue($loaded_media_type->thumbnailDownloadsAreQueued());
$this->assertFalse($loaded_media_type->getStatus());
$this->assertEquals($loaded_media_type->getFieldMap(), ['attribute_1' => 'name']);
// We need to clear the statically cached field definitions to account for
// fields that have been created by API calls in this test, since they exist
// in a separate memory space from the web server.
$this->container->get('entity_field.manager')->clearCachedFieldDefinitions();
// Test that a media item being created with default status to "FALSE",
// will be created unpublished.
/** @var \Drupal\media\MediaInterface $unpublished_media */
$unpublished_media = Media::create(['name' => 'unpublished test media', 'bundle' => $loaded_media_type->id()]);
$this->assertFalse($unpublished_media->isPublished());
$unpublished_media->delete();
// Tests media type delete form.
$page->clickLink('Delete');
$assert_session->addressEquals('admin/structure/media/manage/' . $this->testMediaType->id() . '/delete');
$page->pressButton('Delete');
$assert_session->addressEquals('admin/structure/media');
$assert_session->pageTextContains('The media type ' . $new_name . ' has been deleted.');
// Test that the system for preventing the deletion of media types works
// (they cannot be deleted if there is media content of that type/bundle).
$media_type2 = $this->createMediaType();
$label2 = $media_type2->label();
$media = Media::create(['name' => 'lorem ipsum', 'bundle' => $media_type2->id()]);
$media->save();
$this->drupalGet('admin/structure/media/manage/' . $media_type2->id());
$page->clickLink('Delete');
$assert_session->addressEquals('admin/structure/media/manage/' . $media_type2->id() . '/delete');
$assert_session->buttonNotExists('edit-submit');
$assert_session->pageTextContains("$label2 is used by 1 media item on your site. You can not remove this media type until you have removed all of the $label2 media items.");
}
}
@@ -0,0 +1,87 @@
<?php
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\views\Views;
/**
* Tests the media entity type integration into the wizard.
*
* @group media
*
* @see \Drupal\media\Plugin\views\wizard\Media
* @see \Drupal\media\Plugin\views\wizard\MediaRevision
*/
class MediaViewsWizardTest extends MediaJavascriptTestBase {
/**
* Tests adding a view of media.
*/
public function testMediaWizard() {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$view_id = strtolower($this->randomMachineName(16));
$this->drupalGet('admin/structure/views/add');
$page->fillField('label', $view_id);
$this->waitUntilVisible('.machine-name-value');
$page->selectFieldOption('show[wizard_key]', 'media');
$result = $assert_session->waitForElementVisible('css', 'select[data-drupal-selector="edit-show-type"]');
$this->assertNotEmpty($result);
$page->checkField('page[create]');
$page->fillField('page[path]', $this->randomMachineName(16));
$page->pressButton('Save and edit');
$this->assertEquals($session->getCurrentUrl(), $this->baseUrl . '/admin/structure/views/view/' . $view_id);
$view = Views::getView($view_id);
$view->initHandlers();
$row = $view->display_handler->getOption('row');
$this->assertEquals($row['type'], 'fields');
// Check for the default filters.
$this->assertEquals($view->filter['status']->table, 'media_field_data');
$this->assertEquals($view->filter['status']->field, 'status');
$this->assertTrue($view->filter['status']->value);
// Check for the default fields.
$this->assertEquals($view->field['name']->table, 'media_field_data');
$this->assertEquals($view->field['name']->field, 'name');
}
/**
* Tests adding a view of media revisions.
*/
public function testMediaRevisionWizard() {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$view_id = strtolower($this->randomMachineName(16));
$this->drupalGet('admin/structure/views/add');
$page->fillField('label', $view_id);
$this->waitUntilVisible('.machine-name-value');
$page->selectFieldOption('show[wizard_key]', 'media_revision');
$assert_session->assertWaitOnAjaxRequest();
$page->checkField('page[create]');
$page->fillField('page[path]', $this->randomMachineName(16));
$page->pressButton('Save and edit');
$this->assertEquals($session->getCurrentUrl(), $this->baseUrl . '/admin/structure/views/view/' . $view_id);
$view = Views::getView($view_id);
$view->initHandlers();
$row = $view->display_handler->getOption('row');
$this->assertEquals($row['type'], 'fields');
// Check for the default filters.
$this->assertEquals($view->filter['status']->table, 'media_field_revision');
$this->assertEquals($view->filter['status']->field, 'status');
$this->assertTrue($view->filter['status']->value);
// Check for the default fields.
$this->assertEquals($view->field['name']->table, 'media_field_revision');
$this->assertEquals($view->field['name']->field, 'name');
$this->assertEquals($view->field['changed']->table, 'media_field_revision');
$this->assertEquals($view->field['changed']->field, 'changed');
}
}
@@ -0,0 +1,58 @@
<?php
namespace Drupal\Tests\media\Kernel;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
/**
* Tests creation of media types and media items.
*
* @group media
*/
class MediaCreationTest extends MediaKernelTestBase {
/**
* Tests creating a media type programmatically.
*/
public function testMediaTypeCreation() {
$media_type_storage = $this->container->get('entity_type.manager')->getStorage('media_type');
$this->assertInstanceOf(MediaTypeInterface::class, MediaType::load($this->testMediaType->id()), 'The new media type has not been correctly created in the database.');
// Test a media type created from default configuration.
$this->container->get('module_installer')->install(['media_test_type']);
$test_media_type = $media_type_storage->load('test');
$this->assertInstanceOf(MediaTypeInterface::class, $test_media_type, 'The media type from default configuration has not been created in the database.');
$this->assertEquals('Test type', $test_media_type->get('label'), 'Could not assure the correct type name.');
$this->assertEquals('Test type.', $test_media_type->get('description'), 'Could not assure the correct type description.');
$this->assertEquals('test', $test_media_type->get('source'), 'Could not assure the correct media source.');
// Source field is not set on the media source, but it should never
// be created automatically when a config is being imported.
$this->assertEquals(['source_field' => '', 'test_config_value' => 'Kakec'], $test_media_type->get('source_configuration'), 'Could not assure the correct media source configuration.');
$this->assertEquals(['metadata_attribute' => 'field_attribute_config_test'], $test_media_type->get('field_map'), 'Could not assure the correct field map.');
}
/**
* Tests creating a media item programmatically.
*/
public function testMediaEntityCreation() {
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Unnamed',
'field_media_test' => 'Nation of sheep, ruled by wolves, owned by pigs.',
]);
$media->save();
$this->assertNotInstanceOf(MediaInterface::class, Media::load(rand(1000, 9999)), 'Failed asserting a non-existent media.');
$this->assertInstanceOf(MediaInterface::class, Media::load($media->id()), 'The new media item has not been created in the database.');
$this->assertEquals($this->testMediaType->id(), $media->bundle(), 'The media item was not created with the correct type.');
$this->assertEquals('Unnamed', $media->getName(), 'The media item was not created with the correct name.');
$source_field_name = $media->bundle->entity->getSource()->getSourceFieldDefinition($media->bundle->entity)->getName();
$this->assertEquals('Nation of sheep, ruled by wolves, owned by pigs.', $media->get($source_field_name)->value, 'Source returns incorrect source field value.');
}
}
@@ -0,0 +1,150 @@
<?php
namespace Drupal\Tests\media\Kernel;
use Drupal\file\Entity\File;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaTypeInterface;
use Drupal\user\Entity\User;
use org\bovigo\vfs\vfsStream;
/**
* Base class for Media kernel tests.
*/
abstract class MediaKernelTestBase extends KernelTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = [
'media',
'media_test_source',
'image',
'user',
'field',
'system',
'file',
];
/**
* The test media type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testMediaType;
/**
* The test media type with constraints.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testConstraintsMediaType;
/**
* A user.
*
* @var \Drupal\user\UserInterface
*/
protected $user;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('file');
$this->installSchema('file', 'file_usage');
$this->installSchema('system', 'sequences');
$this->installEntitySchema('media');
$this->installConfig(['field', 'system', 'image', 'file', 'media']);
// Create a test media type.
$this->testMediaType = $this->createMediaType('test');
// Create a test media type with constraints.
$this->testConstraintsMediaType = $this->createMediaType('test_constraints');
$this->user = User::create([
'name' => 'username',
'status' => 1,
]);
$this->user->save();
$this->container->get('current_user')->setAccount($this->user);
}
/**
* Create a media type for a source plugin.
*
* @param string $media_source_name
* The name of the media source.
*
* @return \Drupal\media\MediaTypeInterface
* A media type.
*/
protected function createMediaType($media_source_name) {
$id = strtolower($this->randomMachineName());
$media_type = MediaType::create([
'id' => $id,
'label' => $id,
'source' => $media_source_name,
'new_revision' => FALSE,
]);
$media_type->save();
$source_field = $media_type->getSource()->createSourceField($media_type);
// The media type form creates a source field if it does not exist yet. The
// same must be done in a kernel test, since it does not use that form.
// @see \Drupal\media\MediaTypeForm::save()
$source_field->getFieldStorageDefinition()->save();
// The source field storage has been created, now the field can be saved.
$source_field->save();
$media_type->set('source_configuration', [
'source_field' => $source_field->getName(),
])->save();
return $media_type;
}
/**
* Helper to generate media entity.
*
* @param string $filename
* String filename with extension.
* @param \Drupal\media\MediaTypeInterface $media_type
* The the media type.
*
* @return \Drupal\media\Entity\Media
* A media entity.
*/
protected function generateMedia($filename, MediaTypeInterface $media_type) {
vfsStream::setup('drupal_root');
vfsStream::create([
'sites' => [
'default' => [
'files' => [
$filename => str_repeat('a', 3000),
],
],
],
]);
$file = File::create([
'uri' => 'vfs://drupal_root/sites/default/files/' . $filename,
'uid' => $this->user->id(),
]);
$file->setPermanent();
$file->save();
return Media::create([
'bundle' => $media_type->id(),
'name' => 'Mr. Jones',
'field_media_file' => [
'target_id' => $file->id(),
],
]);
}
}
@@ -0,0 +1,27 @@
<?php
namespace Drupal\Tests\media\Kernel;
use Drupal\media\Entity\Media;
/**
* Tests link relationships for Media entity.
*
* @group media
*/
class MediaLinkRelationsTest extends MediaKernelTestBase {
/**
* Tests that all link relationships for Media exist.
*/
public function testExistLinkRelationships() {
/** @var \Drupal\Core\Http\LinkRelationTypeManager $link_relation_type_manager */
$link_relation_type_manager = $this->container->get('plugin.manager.link_relation_type');
$media = Media::create(['bundle' => $this->testMediaType->id()]);
$media->save();
foreach ($media->uriRelationships() as $relation_name) {
$this->assertTrue($link_relation_type_manager->hasDefinition($relation_name), "Link relationship '{$relation_name}' for Media entity");
}
}
}
@@ -0,0 +1,44 @@
<?php
namespace Drupal\Tests\media\Kernel;
use Drupal\media\Entity\MediaType;
/**
* Tests the file media source.
*
* @group media
*/
class MediaSourceFileTest extends MediaKernelTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// We need to test without any default configuration in place.
// @TODO: Remove this as part of https://www.drupal.org/node/2883813.
MediaType::load('file')->delete();
MediaType::load('image')->delete();
}
/**
* Tests the file extension constraint.
*/
public function testFileExtensionConstraint() {
$mediaType = $this->createMediaType('file');
// Create a random file that should fail.
$media = $this->generateMedia('test.patch', $mediaType);
$result = $media->validate();
$this->assertCount(1, $result);
$this->assertEquals('field_media_file.0', $result->get(0)->getPropertyPath());
$this->assertContains('Only files with the following extensions are allowed:', (string) $result->get(0)->getMessage());
// Create a random file that should pass.
$media = $this->generateMedia('test.txt', $mediaType);
$result = $media->validate();
$this->assertCount(0, $result);
}
}
@@ -0,0 +1,445 @@
<?php
namespace Drupal\Tests\media\Kernel;
use Drupal\Core\Form\FormState;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
/**
* Tests media source plugins related logic.
*
* @group media
*/
class MediaSourceTest extends MediaKernelTestBase {
/**
* Tests default media name functionality.
*/
public function testDefaultName() {
// Make sure that the default name is set if not provided by the user.
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create(['bundle' => $this->testMediaType->id()]);
$media_source = $media->getSource();
$this->assertEquals('default_name', $media_source->getPluginDefinition()['default_name_metadata_attribute'], 'Default metadata attribute is not used for the default name.');
$this->assertEquals('media:' . $media->bundle() . ':' . $media->uuid(), $media_source->getMetadata($media, 'default_name'), 'Value of the default name metadata attribute does not look correct.');
$this->assertEquals('media:' . $media->bundle() . ':' . $media->uuid(), $media->getName(), 'Default name was not used correctly by getName().');
$this->assertEquals($media->getName(), $media->label(), 'Default name and label are not the same.');
$media->save();
$this->assertEquals('media:' . $media->bundle() . ':' . $media->uuid(), $media->getName(), 'Default name was not saved correctly.');
$this->assertEquals($media->getName(), $media->label(), 'The label changed during save.');
// Make sure that the user-supplied name is used.
/** @var \Drupal\media\MediaInterface $media */
$name = 'User-supplied name';
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => $name,
]);
$media_source = $media->getSource();
$this->assertEquals('default_name', $media_source->getPluginDefinition()['default_name_metadata_attribute'], 'Default metadata attribute is not used for the default name.');
$this->assertEquals('media:' . $media->bundle() . ':' . $media->uuid(), $media_source->getMetadata($media, 'default_name'), 'Value of the default name metadata attribute does not look correct.');
$media->save();
$this->assertEquals($name, $media->getName(), 'User-supplied name was not set correctly.');
$this->assertEquals($media->getName(), $media->label(), 'The user-supplied name does not match the label.');
// Change the default name attribute and see if it is used to set the name.
$name = 'Old Major';
\Drupal::state()->set('media_source_test_attributes', ['alternative_name' => ['title' => 'Alternative name', 'value' => $name]]);
\Drupal::state()->set('media_source_test_definition', ['default_name_metadata_attribute' => 'alternative_name']);
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create(['bundle' => $this->testMediaType->id()]);
$media_source = $media->getSource();
$this->assertEquals('alternative_name', $media_source->getPluginDefinition()['default_name_metadata_attribute'], 'Correct metadata attribute is not used for the default name.');
$this->assertEquals($name, $media_source->getMetadata($media, 'alternative_name'), 'Value of the default name metadata attribute does not look correct.');
$media->save();
$this->assertEquals($name, $media->getName(), 'Default name was not set correctly.');
$this->assertEquals($media->getName(), $media->label(), 'The default name does not match the label.');
}
/**
* Tests metadata mapping functionality.
*/
public function testMetadataMapping() {
$field_name = 'field_to_map_to';
$attribute_name = 'attribute_to_map';
$storage = FieldStorageConfig::create([
'entity_type' => 'media',
'field_name' => $field_name,
'type' => 'string',
]);
$storage->save();
FieldConfig::create([
'field_storage' => $storage,
'bundle' => $this->testMediaType->id(),
'label' => 'Field to map to',
])->save();
// Save the entity without defining the metadata mapping and check that the
// field stays empty.
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'field_media_test' => 'some_value',
]);
$media->save();
$this->assertEmpty($media->get($field_name)->value, 'Field stayed empty.');
// Make sure that source plugin returns NULL for non-existing fields.
$this->testMediaType->setFieldMap(['not_here_at_all' => $field_name])->save();
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'field_media_test' => 'some_value',
]);
$media_source = $media->getSource();
$this->assertNull($media_source->getMetadata($media, 'not_here_at_all'), 'NULL is not returned if asking for a value of non-existing metadata.');
$media->save();
$this->assertTrue($media->get($field_name)->isEmpty(), 'Non-existing metadata attribute was wrongly mapped to the field.');
// Define mapping and make sure that the value was stored in the field.
\Drupal::state()->set('media_source_test_attributes', [
$attribute_name => ['title' => 'Attribute to map', 'value' => 'Snowball'],
]);
$this->testMediaType->setFieldMap([$attribute_name => $field_name])->save();
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'field_media_test' => 'some_value',
]);
$media_source = $media->getSource();
$this->assertEquals('Snowball', $media_source->getMetadata($media, $attribute_name), 'Value of the metadata attribute is not correct.');
$media->save();
$this->assertEquals('Snowball', $media->get($field_name)->value, 'Metadata attribute was not mapped to the field.');
// Change the metadata attribute value and re-save the entity. Field value
// should stay the same.
\Drupal::state()->set('media_source_test_attributes', [
$attribute_name => ['title' => 'Attribute to map', 'value' => 'Pinkeye'],
]);
$this->assertEquals('Pinkeye', $media_source->getMetadata($media, $attribute_name), 'Value of the metadata attribute is not correct.');
$media->save();
$this->assertEquals('Snowball', $media->get($field_name)->value, 'Metadata attribute was not mapped to the field.');
// Now change the value of the source field and make sure that the mapped
// values update too.
$this->assertEquals('Pinkeye', $media_source->getMetadata($media, $attribute_name), 'Value of the metadata attribute is not correct.');
$media->set('field_media_test', 'some_new_value');
$media->save();
$this->assertEquals('Pinkeye', $media->get($field_name)->value, 'Metadata attribute was not mapped to the field.');
// Remove the value of the mapped field and make sure that it is re-mapped
// on save.
\Drupal::state()->set('media_source_test_attributes', [
$attribute_name => ['title' => 'Attribute to map', 'value' => 'Snowball'],
]);
$media->{$field_name}->value = NULL;
$this->assertEquals('Snowball', $media_source->getMetadata($media, $attribute_name), 'Value of the metadata attribute is not correct.');
$media->save();
$this->assertEquals('Snowball', $media->get($field_name)->value, 'Metadata attribute was not mapped to the field.');
}
/**
* Tests the thumbnail functionality.
*/
public function testThumbnail() {
file_put_contents('public://thumbnail1.jpg', '');
file_put_contents('public://thumbnail2.jpg', '');
// Save a media entity and make sure thumbnail was added.
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['title' => 'Thumbnail', 'value' => 'public://thumbnail1.jpg'],
]);
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Mr. Jones',
'field_media_test' => 'some_value',
]);
$media_source = $media->getSource();
$this->assertEquals('public://thumbnail1.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertEquals('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was not added to the media entity.');
$this->assertEquals('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
// Now change the metadata attribute and make sure that the thumbnail stays
// the same.
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['title' => 'Thumbnail', 'value' => 'public://thumbnail2.jpg'],
]);
$this->assertEquals('public://thumbnail2.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertEquals('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was not preserved.');
$this->assertEquals('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
// Remove the thumbnail and make sure that it is auto-updated on save.
$media->thumbnail->target_id = NULL;
$this->assertEquals('public://thumbnail2.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertEquals('public://thumbnail2.jpg', $media->thumbnail->entity->getFileUri(), 'New thumbnail was not added to the media entity.');
$this->assertEquals('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
// Change the metadata attribute again, change the source field value too
// and make sure that the thumbnail updates.
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['title' => 'Thumbnail', 'value' => 'public://thumbnail1.jpg'],
]);
$media->field_media_test->value = 'some_new_value';
$this->assertEquals('public://thumbnail1.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertEquals('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'New thumbnail was not added to the media entity.');
$this->assertEquals('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
// Change the thumbnail metadata attribute and make sure that the thumbnail
// is set correctly.
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['title' => 'Should not be used', 'value' => 'public://thumbnail1.jpg'],
'alternative_thumbnail_uri' => ['title' => 'Should be used', 'value' => 'public://thumbnail2.jpg'],
]);
\Drupal::state()->set('media_source_test_definition', ['thumbnail_uri_metadata_attribute' => 'alternative_thumbnail_uri']);
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Mr. Jones',
'field_media_test' => 'some_value',
]);
$media_source = $media->getSource();
$this->assertEquals('public://thumbnail1.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the metadata attribute is not correct.');
$this->assertEquals('public://thumbnail2.jpg', $media_source->getMetadata($media, 'alternative_thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertEquals('public://thumbnail2.jpg', $media->thumbnail->entity->getFileUri(), 'Correct metadata attribute was not used for the thumbnail.');
$this->assertEquals('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
// Enable queued thumbnails and make sure that the entity gets the default
// thumbnail initially.
\Drupal::state()->set('media_source_test_definition', []);
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['title' => 'Should not be used', 'value' => 'public://thumbnail1.jpg'],
]);
$this->testMediaType->setQueueThumbnailDownloadsStatus(TRUE)->save();
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Mr. Jones',
'field_media_test' => 'some_value',
]);
$this->assertEquals('public://thumbnail1.jpg', $media->getSource()->getMetadata($media, 'thumbnail_uri'), 'Value of the metadata attribute is not correct.');
$media->save();
$this->assertEquals('public://media-icons/generic/generic.png', $media->thumbnail->entity->getFileUri(), 'Default thumbnail was not set initially.');
$this->assertEquals('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
// Process the queue item and make sure that the thumbnail was updated too.
$queue_name = 'media_entity_thumbnail';
/** @var \Drupal\Core\Queue\QueueWorkerInterface $queue_worker */
$queue_worker = \Drupal::service('plugin.manager.queue_worker')->createInstance($queue_name);
$queue = \Drupal::queue($queue_name);
$this->assertEquals(1, $queue->numberOfItems(), 'Item was not added to the queue.');
$item = $queue->claimItem();
$this->assertEquals($media->id(), $item->data['id'], 'Queue item that was created does not belong to the correct entity.');
$queue_worker->processItem($item->data);
$queue->deleteItem($item);
$this->assertEquals(0, $queue->numberOfItems(), 'Item was not removed from the queue.');
$media = Media::load($media->id());
$this->assertEquals('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was not updated by the queue.');
$this->assertEquals('Mr. Jones', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
$this->assertEquals('Thumbnail', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
// Set alt and title metadata attributes and make sure they are used for the
// thumbnail.
\Drupal::state()->set('media_source_test_definition', [
'thumbnail_alt_metadata_attribute' => 'alt',
'thumbnail_title_metadata_attribute' => 'title',
]);
\Drupal::state()->set('media_source_test_attributes', [
'alt' => ['title' => 'Alt text', 'value' => 'This will be alt.'],
'title' => ['title' => 'Title text', 'value' => 'This will be title.'],
]);
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Boxer',
'field_media_test' => 'some_value',
]);
$media->save();
$this->assertEquals('Boxer', $media->getName(), 'Correct name was not set on the media entity.');
$this->assertEquals('This will be title.', $media->thumbnail->title, 'Title text was not set on the thumbnail.');
$this->assertEquals('This will be alt.', $media->thumbnail->alt, 'Alt text was not set on the thumbnail.');
}
/**
* Tests the media entity constraints functionality.
*/
public function testConstraints() {
// Test entity constraints.
\Drupal::state()->set('media_source_test_entity_constraints', [
'MediaTestConstraint' => [],
]);
// Create a media item media that uses a source plugin with constraints and
// make sure the constraints works as expected when validating.
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'bundle' => $this->testConstraintsMediaType->id(),
'name' => 'I do not like Drupal',
'field_media_test_constraints' => 'Not checked',
]);
// Validate the entity and make sure violation is reported.
/** @var \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations */
$violations = $media->validate();
$this->assertCount(1, $violations, 'Expected number of validations not found.');
$this->assertEquals('Inappropriate text.', $violations->get(0)->getMessage(), 'Incorrect constraint validation message found.');
// Fix the violation and make sure it is not reported anymore.
$media->setName('I love Drupal!');
$violations = $media->validate();
$this->assertCount(0, $violations, 'Expected number of validations not found.');
// Save and make sure it succeeded.
$this->assertEmpty($media->id(), 'Entity ID was found.');
$media->save();
$this->assertNotEmpty($media->id(), 'Entity ID was not found.');
$this->assertSame($media->getName(), 'I love Drupal!');
// Test source field constraints.
\Drupal::state()->set('media_source_test_field_constraints', [
'MediaTestConstraint' => [],
]);
\Drupal::state()->set('media_source_test_entity_constraints', []);
// Create media that uses source with constraints and make sure it can't
// be saved without validating them.
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'bundle' => $this->testConstraintsMediaType->id(),
'name' => 'Not checked',
'field_media_test_constraints' => 'I do not like Drupal',
]);
// Validate the entity and make sure violation is reported.
/** @var \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations */
$violations = $media->validate();
$this->assertCount(1, $violations, 'Expected number of validations not found.');
$this->assertEquals('Inappropriate text.', $violations->get(0)->getMessage(), 'Incorrect constraint validation message found.');
// Fix the violation and make sure it is not reported anymore.
$media->set('field_media_test_constraints', 'I love Drupal!');
$violations = $media->validate();
$this->assertCount(0, $violations, 'Expected number of validations not found.');
// Save and make sure it succeeded.
$this->assertEmpty($media->id(), 'Entity ID was found.');
$media->save();
$this->assertNotEmpty($media->id(), 'Entity ID was not found.');
}
/**
* Tests logic related to the automated source field creation.
*/
public function testSourceFieldCreation() {
/** @var \Drupal\media\MediaTypeInterface $type */
$type = MediaType::create([
'id' => 'test_type',
'label' => 'Test type',
'source' => 'test',
]);
/** @var \Drupal\field\Entity\FieldConfig $field */
$field = $type->getSource()->createSourceField($type);
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
$field_storage = $field->getFieldStorageDefinition();
// Test field storage.
$this->assertTrue($field_storage->isNew(), 'Field storage is saved automatically.');
$this->assertFalse($field_storage->isLocked(), 'Field storage is not locked.');
$this->assertEquals('string', $field_storage->getType(), 'Field is not of correct type.');
$this->assertEquals('field_media_test_1', $field_storage->getName(), 'Incorrect field name is used.');
$this->assertEquals('media', $field_storage->getTargetEntityTypeId(), 'Field is not targeting media entities.');
// Test field.
$this->assertTrue($field->isNew(), 'Field is saved automatically.');
$this->assertEquals('field_media_test_1', $field->getName(), 'Incorrect field name is used.');
$this->assertEquals('string', $field->getType(), 'Field is of incorrect type.');
$this->assertTrue($field->isRequired(), 'Field is not required.');
$this->assertEquals('Test source', $field->label(), 'Incorrect label is used.');
$this->assertEquals('test_type', $field->getTargetBundle(), 'Field is not targeting correct bundle.');
// Fields should be automatically saved only when creating the media type
// using the media type creation form. Make sure that they are not saved
// when creating a media type programmatically.
// Drupal\Tests\media\FunctionalJavascript\MediaTypeCreationTest is testing
// form part of the functionality.
$type->save();
$storage = FieldStorageConfig::load('media.field_media_test_1');
$this->assertNull($storage, 'Field storage was not saved.');
$field = FieldConfig::load('media.test_type.field_media_test_1');
$this->assertNull($field, 'Field storage was not saved.');
// Test the plugin with a different default source field type.
$type = MediaType::create([
'id' => 'test_constraints_type',
'label' => 'Test type with constraints',
'source' => 'test_constraints',
]);
$field = $type->getSource()->createSourceField($type);
$field_storage = $field->getFieldStorageDefinition();
// Test field storage.
$this->assertTrue($field_storage->isNew(), 'Field storage is saved automatically.');
$this->assertFalse($field_storage->isLocked(), 'Field storage is not locked.');
$this->assertEquals('string_long', $field_storage->getType(), 'Field is of incorrect type.');
$this->assertEquals('field_media_test_constraints_1', $field_storage->getName(), 'Incorrect field name is used.');
$this->assertEquals('media', $field_storage->getTargetEntityTypeId(), 'Field is not targeting media entities.');
// Test field.
$this->assertTrue($field->isNew(), 'Field is saved automatically.');
$this->assertEquals('field_media_test_constraints_1', $field->getName(), 'Incorrect field name is used.');
$this->assertEquals('string_long', $field->getType(), 'Field is of incorrect type.');
$this->assertTrue($field->isRequired(), 'Field is not required.');
$this->assertEquals('Test source with constraints', $field->label(), 'Incorrect label is used.');
$this->assertEquals('test_constraints_type', $field->getTargetBundle(), 'Field is not targeting correct bundle.');
}
/**
* Tests configuration form submit handler on the base media source plugin.
*/
public function testSourceConfigurationSubmit() {
/** @var \Drupal\media\MediaSourceManager $manager */
$manager = $this->container->get('plugin.manager.media.source');
$form = [];
$form_state = new FormState();
$form_state->setValues(['test_config_value' => 'Somewhere over the rainbow.']);
/** @var \Drupal\media\MediaSourceInterface $source */
$source = $manager->createInstance('test', []);
$source->submitConfigurationForm($form, $form_state);
$expected = ['test_config_value' => 'Somewhere over the rainbow.', 'source_field' => 'field_media_test_1'];
$this->assertEquals($expected, $source->getConfiguration(), 'Submitted values were saved correctly.');
// Try to save a NULL value.
$form_state->setValue('test_config_value', NULL);
$source->submitConfigurationForm($form, $form_state);
$expected['test_config_value'] = NULL;
$this->assertEquals($expected, $source->getConfiguration(), 'Submitted values were saved correctly.');
// Make sure that the config keys are determined correctly even if the
// existing value is NULL.
$form_state->setValue('test_config_value', 'Somewhere over the rainbow.');
$source->submitConfigurationForm($form, $form_state);
$expected['test_config_value'] = 'Somewhere over the rainbow.';
$this->assertEquals($expected, $source->getConfiguration(), 'Submitted values were saved correctly.');
// Make sure that a non-relevant value will be skipped.
$form_state->setValue('not_relevant', 'Should not be saved in the plugin.');
$source->submitConfigurationForm($form, $form_state);
$this->assertEquals($expected, $source->getConfiguration(), 'Submitted values were saved correctly.');
}
}
@@ -0,0 +1,23 @@
<?php
namespace Drupal\Tests\media\Kernel;
use Drupal\media\Entity\Media;
/**
* Tests Media.
*
* @group media
*/
class MediaTest extends MediaKernelTestBase {
/**
* Tests various aspects of a Media entity.
*/
public function testEntity() {
$media = Media::create(['bundle' => $this->testMediaType->id()]);
$this->assertSame($media, $media->setOwnerId($this->user->id()), 'setOwnerId() method returns its own entity.');
}
}
@@ -0,0 +1,103 @@
<?php
namespace Drupal\Tests\media\Kernel;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests multilanguage fields logic.
*
* @group media
*/
class MediaTranslationTest extends MediaKernelTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['language'];
/**
* The test media translation type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testTranslationMediaType;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(['language']);
// Create a test media type for translations.
$this->testTranslationMediaType = $this->createMediaType('test_translation');
for ($i = 0; $i < 3; ++$i) {
$language_id = 'l' . $i;
ConfigurableLanguage::create([
'id' => $language_id,
'label' => $this->randomString(),
])->save();
file_put_contents('public://' . $language_id . '.png', '');
}
}
/**
* Test translatable fields storage/retrieval.
*/
public function testTranslatableFieldSaveLoad() {
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
$entity_type = $this->container->get('entity_type.manager')->getDefinition('media');
$this->assertTrue($entity_type->isTranslatable(), 'Media is translatable.');
// Prepare the field translations.
$source_field_definition = $this->testTranslationMediaType->getSource()->getSourceFieldDefinition($this->testTranslationMediaType);
$source_field_storage = $source_field_definition->getFieldStorageDefinition();
/** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $media_storage */
$media_storage = $this->container->get('entity_type.manager')->getStorage('media');
/** @var \Drupal\media\Entity\Media $media */
$media = $media_storage->create([
'bundle' => $this->testTranslationMediaType->id(),
'name' => 'Unnamed',
]);
$field_translations = [];
$available_langcodes = array_keys($this->container->get('language_manager')->getLanguages());
$media->set('langcode', reset($available_langcodes));
foreach ($available_langcodes as $langcode) {
$values = [];
for ($i = 0; $i < $source_field_storage->getCardinality(); $i++) {
$values[$i]['value'] = $this->randomString();
}
$field_translations[$langcode] = $values;
$translation = $media->hasTranslation($langcode) ? $media->getTranslation($langcode) : $media->addTranslation($langcode);
$translation->{$source_field_definition->getName()}->setValue($field_translations[$langcode]);
}
// Save and reload the field translations.
$media->save();
$media_storage->resetCache();
$media = $media_storage->load($media->id());
// Check if the correct source field values were saved/loaded.
foreach ($field_translations as $langcode => $items) {
/** @var \Drupal\media\MediaInterface $media_translation */
$media_translation = $media->getTranslation($langcode);
$result = TRUE;
foreach ($items as $delta => $item) {
$result = $result && $item['value'] == $media_translation->{$source_field_definition->getName()}[$delta]->value;
}
$this->assertTrue($result, new FormattableMarkup('%language translation field value not correct.', ['%language' => $langcode]));
$this->assertEquals('public://' . $langcode . '.png', $media_translation->getSource()->getMetadata($media_translation, 'thumbnail_uri'), new FormattableMarkup('%language translation thumbnail metadata attribute is not correct.', ['%language' => $langcode]));
$this->assertEquals('public://' . $langcode . '.png', $media_translation->get('thumbnail')->entity->getFileUri(), new FormattableMarkup('%language translation thumbnail value is not correct.', ['%language' => $langcode]));
$this->assertEquals('Test Thumbnail ' . $langcode, $media_translation->getSource()->getMetadata($media_translation, 'test_thumbnail_alt'), new FormattableMarkup('%language translation thumbnail alt metadata attribute is not correct.', ['%language' => $langcode]));
$this->assertEquals('Test Thumbnail ' . $langcode, $media_translation->get('thumbnail')->alt, new FormattableMarkup('%language translation thumbnail alt value is not correct.', ['%language' => $langcode]));
}
}
}