Commit 8c361322 authored by catch's avatar catch

Issue #2831936 by marcoscano, seanB, chr.fritsch, l0ke, Wim Leers, vijaycs85,...

Issue #2831936 by marcoscano, seanB, chr.fritsch, l0ke, Wim Leers, vijaycs85, phenaproxima, pguillard, johndevman, drubb: Add "File" MediaSource plugin
parent fde3369b
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: { }
uid:
type: entity_reference_autocomplete
weight: 5
settings:
match_operator: CONTAINS
size: 60
placeholder: ''
region: content
third_party_settings: { }
hidden: { }
langcode: en
status: true
dependencies:
config:
- field.field.media.file.field_media_file
- image.style.thumbnail
- media.type.file
module:
- file
- image
- user
id: media.file.default
targetEntityType: media
bundle: file
mode: default
content:
created:
label: hidden
type: timestamp
weight: 0
region: content
settings:
date_format: medium
custom_date_format: ''
timezone: ''
third_party_settings: { }
field_media_file:
label: above
settings: { }
third_party_settings: { }
type: file_default
weight: 6
region: content
thumbnail:
type: image
weight: 5
label: hidden
settings:
image_style: thumbnail
image_link: ''
region: content
third_party_settings: { }
uid:
label: hidden
type: author
weight: 0
region: content
settings: { }
third_party_settings: { }
hidden: { }
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 doc docx pdf'
max_filesize: ''
handler: 'default:file'
handler_settings: { }
description_field: false
field_type: file
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: true
cardinality: 1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
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: { }
......@@ -44,6 +44,10 @@ media.source.*:
type: mapping
label: 'Media source settings'
media.source.file:
type: media.source.field_aware
label: '"File" media source configuration'
media.source.field_aware:
type: mapping
mapping:
......
core/modules/media/images/icons/generic.png

670 Bytes | W: | H:

core/modules/media/images/icons/generic.png

669 Bytes | W: | H:

core/modules/media/images/icons/generic.png
core/modules/media/images/icons/generic.png
core/modules/media/images/icons/generic.png
core/modules/media/images/icons/generic.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -27,6 +27,15 @@ function media_install() {
}
}
/**
* Implements hook_uninstall().
*
* @TODO Remove when https://www.drupal.org/node/2884202 is fixed.
*/
function media_uninstall() {
\Drupal::moduleHandler()->invoke('field', 'cron');
}
/**
* Implements hook_requirements().
*/
......
<?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']);
}
}
<?php
namespace Drupal\Tests\media\Functional;
use Drupal\Core\Entity\EntityInterface;
/**
* Tests the revisionability of media entities.
*
* @group media
*/
class MediaRevisionTest extends MediaFunctionalTestBase {
/**
* 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 and publish');
$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 and keep published');
$this->assertRevisionCount($media, 2);
}
/**
* 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);
}
}
......@@ -3,6 +3,7 @@
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
/**
* Ensures that media UI works correctly.
......@@ -28,6 +29,10 @@ 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();
}
/**
......
<?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));
$assert_session->assertWaitOnAjaxRequest();
$page->pressButton('Save and publish');
$assert_session->addressEquals('media/1');
// Make sure the thumbnail is displayed.
$assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'generic.png');
// Load the media and check if the label was properly populated.
$media = Media::load(1);
$this->assertEquals($test_filename, $media->label());
// 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));
$assert_session->assertWaitOnAjaxRequest();
$page->pressButton('Save and publish');
$assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'text--plain.png');
}
}
<?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, []);
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity_form_display */
$entity_form_display = entity_get_form_display('media', $media_type_id, 'default');
$entity_form_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);
$assert_session->assertWaitOnAjaxRequest();
// Make sure the provided fields are visible on the form.
foreach ($provided_fields as $provided_field) {
$assert_session->selectExists("field_map[{$provided_field}]");
}
// Save the form to create the type.
$page->pressButton('Save');
$assert_session->statusCodeEquals(200);
$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);
}
}
......@@ -38,6 +38,10 @@ 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();
}
/**
......
......@@ -2,8 +2,12 @@
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 org\bovigo\vfs\vfsStream;
/**
* Base class for Media kernel tests.
......@@ -88,4 +92,42 @@ protected function createMediaType($media_source_name) {
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,
]);
$file->setPermanent();
$file->save();
return Media::create([
'bundle' => $media_type->id(),
'name' => 'Mr. Jones',
'field_media_file' => [
'target_id' => $file->id(),
],
]);
}
}
<?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();
}
/**
* 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);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment