diff --git a/core/modules/media/config/schema/media.schema.yml b/core/modules/media/config/schema/media.schema.yml index 14d40a9065..dad518985a 100644 --- a/core/modules/media/config/schema/media.schema.yml +++ b/core/modules/media/config/schema/media.schema.yml @@ -44,6 +44,14 @@ 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: diff --git a/core/modules/media/images/icons/no-thumbnail.png b/core/modules/media/images/icons/no-thumbnail.png new file mode 100644 index 0000000000..496a18479e --- /dev/null +++ b/core/modules/media/images/icons/no-thumbnail.png @@ -0,0 +1,4 @@ +PNG + + IHDRYIDATx@ߠp`A +\@R 9@ףw^bXA  4hРA 4hРA 4hРA 4hРA :?V~u} $this->t('MIME type'), + static::METADATA_ATTRIBUTE_SIZE => $this->t('Size'), + ]; + } + + /** + * {@inheritdoc} + */ + public function getMetadata(MediaInterface $media, $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, $name); + } + switch ($name) { + case 'default_name': + return $file->getFilename(); + + case 'thumbnail_uri': + return $this->getThumbnail($file) ?: parent::getMetadata($media, $name); + + case static::METADATA_ATTRIBUTE_MIME: + return $file->getMimeType() ?: NULL; + + case static::METADATA_ATTRIBUTE_SIZE: + $size = $file->getSize(); + return is_numeric($size) ? $size : NULL; + + default: + return parent::getMetadata($media, $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 = array( + $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; + } + +} diff --git a/core/modules/media/src/Plugin/media/Source/Image.php b/core/modules/media/src/Plugin/media/Source/Image.php new file mode 100644 index 0000000000..706e180f8a --- /dev/null +++ b/core/modules/media/src/Plugin/media/Source/Image.php @@ -0,0 +1,248 @@ +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'), + ]; + + if ($this->canReadExifData()) { + $attributes += [ + 'model' => $this->t('Camera model'), + 'created' => $this->t('Image creation datetime'), + 'iso' => $this->t('ISO'), + 'exposure' => $this->t('Exposure time'), + 'aperture' => $this->t('Aperture value'), + 'focal_length' => $this->t('Focal length'), + ]; + } + + return $attributes; + } + + /** + * {@inheritdoc} + */ + public function getMetadata(MediaInterface $media, $name) { + // Get the file, image and EXIF 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; + } + + if ($this->canReadExifData()) { + switch ($name) { + case 'created': + /** @var \DateTime $date */ + $date = new DrupalDateTime($this->getExifField($uri, 'DateTimeOriginal')); + return $date->getTimestamp(); + + case 'model': + return $this->getExifField($uri, 'Model'); + + case 'iso': + return $this->getExifField($uri, 'ISOSpeedRatings'); + + case 'exposure': + return $this->getExifField($uri, 'ExposureTime'); + + case 'aperture': + return $this->getExifField($uri, 'FNumber'); + + case 'focal_length': + return $this->getExifField($uri, 'FocalLength'); + } + } + + return parent::getMetadata($media, $name); + } + + /** + * {@inheritdoc} + */ + public function buildConfigurationForm(array $form, FormStateInterface $form_state) { + $form = parent::buildConfigurationForm($form, $form_state); + + if (!$this->canReadExifData()) { + $form['no_exif_data_reader'] = [ + '#markup' => $this->t('Unable to read EXIF data for images. In order to provide EXIF data reading functionality please take a look at PHP documentation of the exif_read_data function.'), + ]; + } + + return $form; + } + + /** + * Checks if we can read EXIF data. + * + * @return bool + * Returns TRUE if functionality for reading of EXIF data is provided. + */ + protected function canReadExifData() { + return function_exists('exif_read_data'); + } + + /** + * Gets EXIF field value. + * + * @param string $uri + * The URI for the file that we are getting the EXIF for. + * @param string $field + * The name of the requested EXIF field. + * + * @return string|bool + * The value for the requested field or FALSE if it is not set. + */ + protected function getExifField($uri, $field) { + if (empty($this->exif)) { + $this->exif = $this->getExifData($uri); + } + + return $this->exif[$field] ?: NULL; + } + + /** + * Reads EXIF. + * + * @param string $uri + * The URI for the file that we are getting the EXIF field value. + * + * @return array|bool + * An associative array where the keys are header names and values are + * the values associated with those headers. We return FALSE if the data + * can't be read. + */ + protected function getExifData($uri) { + return exif_read_data($this->fileSystem->realpath($uri), 'EXIF'); + } + +} diff --git a/core/modules/media/tests/fixtures/exif_example_1.jpeg b/core/modules/media/tests/fixtures/exif_example_1.jpeg new file mode 100644 index 0000000000..ac79b36804 --- /dev/null +++ b/core/modules/media/tests/fixtures/exif_example_1.jpeg @@ -0,0 +1,50 @@ +JFIFHH ExifMM*bx(i%Drupal EXIF Camera 1HH02100100NE4` Əhttp://ns.adobe.com/xap/1.0/ + + + + + Drupal EXIF Camera + 72 + 72 + Inch + Centered + Exif Version 2.1 + FlashPix Version 1.0 + Uncalibrated + N + 52, 30, 32.751 + E + 13, 22, 25.5432 + + + + + +C + + +    C   Y HNƋ`:Q&Y9nZl(-p㓟{'h\R@o3uv'N%Wi}cʭ0E-%+[}[/OI^fdp,Ć^_?}:ΞQ]gYh2Z]ՋEgDhf )ɋK0q,LGduV٭N#kOHW+Tס_D;|t!ΪNrvb=%qkMtQ#}vw]vHxzysl_i-vtv¯\.Ыj]]$:o,o>ma_[ +ȔJYԷ+ 콟C!yWK?܋2jU潲˅ˤQX' 6@03PH}jr5fKl\m5 qYqhc i8Ǻ7}Fzetc4 +B/,( %UR:sZy<ٖ.~ɟR`= ^%#?1ZwFh?;3Z#.K֓8#s;qWB/(]iCI =*@/.&{')Mt ZQ5Xn5P𛇤JfKʻ s}%eg"d!?iqIp kůhU衈>=քN6:~J )anjZYp{YgPMR\Q;5vA*>2m YC[~(SHl"ٵk֦%r- ?@Mi7D$lSC`Yt4t77:*8LW~isR7+C(lyԋLW')5UԷzNL4JEEX@=hI¤%p4Ji"$nqՑ@Á5|PT*AB )r)܂>XԬ='l>JSs=¡,|ULJ%! !G\uTgz j34SWƀ`*I@dc" ѧ:5C6Th2iԞ'iS! q(5m͑{fO}yM)Y>tJS] j hKvȓ +"RpVڑFb!Ѵf_sܝf 9¶{CN# <+ШܺEQ4VX'g: !"1A#2Qq 0@a45R$3Pc?QDq}(Oz4*TЎ7ړ +[$^֯BԜ9?ôu:ţ]jrUՆd0RU .%ar ԑ:z`nα eJM#)Dd1` 6OÃUY_v 쿟SumwjOrJ ⧢teN\ 7+4#!w5Cw)~'K{5{t%u$y ]b_J_a)`5Q.,sQ*.< V,$V2ǹ;OM8iM1~ ¤]'GϢr,C3tq6M u(mK(VR0i0,+ mRӪaar6_L[ҏXq0|0_*!5 Oݛʗ&;P>T]Sx=1 +ް)TÉLK >Z]BO/Ʈ($m7~sœFϟ4}i*̖AvK9^bӎiyRFt)EszU\Gp@d:|C !1"AQaq2B #@0RSTbrs$3P? 36S(2wT&kkw~r^Pt7a@$2a,Lˌ:ie #4dD Ǘlk߸oɓ|#nfBU++BBhD&Z}^0 +|Nr]5n / +Juro=U֏J$|PnjqJOخ5XeA{Tc"?ԕѬWU7DZUr?dJm*0ے(OeUv鷘KpG&YNjCvE)WD +i!xQƔn]dja! 90U'! o!T Y/+XS*$mH(AwJ)- БS&0haJkFQNN/Q0*dڽh-8ȴEBwy^ȓߨM'bArGtK!62xD3CJSRxԟl).v$") _Ma]&w\2n)w~9S?Q/6txGL#"^szjV6+Nix\GK&Y҂kw2ᇦՃj5L4f/P6ƧiRGth,S(J x (vjNDzyg_v_[^d _cbe +@Y'L)Lxȥ2ҐG,O}*iE_1Ӡe+Dޛ>3&~f6ML~k8T @"[@-ɥ1Yܸ TbhSJfbm 4}X.N,~MJ6%BV(]w#@Iԓ`Q%޵5b9K1"l +@3%|mZB`yzI}SgJ=0ًz,v4 g]I3 G$I$I$rI$I$*I$I$)Sl$I$Dm%I$Lm%5 so{BjNdrrI$$I$I$AI$I$I$I$I$O*!1AQaq@ 0P?ɘDvҍ;]qV~6XUnjX+yv-Rn^)_K JtA} x֖\.ƄAΞz)J6%7[a-I؁JUf"#zEl!bH7C@+9j u@-lUB:SY qĢ$_ 5*o*Oabj;PÀ-7]=@Dv58]1]37M76MN{U^p|-eאVL wPO:ߍhcC9Q5αu6{W;2U({RAVF +f&+`} #a8ڈA3paDPDUIn#WUb[/sBW"Ѽw4E7 F{kI%t\T],;V+-zik$J!eY|˭,Ɔ=uC33B6H,,̓h-ڇ8ne LYlҏV܋k9kS*qʀ,Ӏǘ LVLbm,c.νP5kEI b3`>?O1a[k + 8D4jz? @KA>_ sȤ`!/3*!1AQaq@ 0P? +n_oB5 xcRNRZ$O/'/=i7q:ڈD"L%<~v^X}eeC†u@Yj'Î^)ǙHʅ3.'e9tyCЏy0KąSTNRFR< m³&gNUybDֳP5ӝB149Nc]: 1_^FVĦtʭRU鹣z4S5}IB +pUȃǖbxGύӒys޼o +1cXaYvlrŇQY<{SI@7Kछ+8s484;E0]+Yy]3پXkСWl"oPd̴sʅԈjE]Rա ÏlFX-k e^bNed&81p,$ RhFdIl~.i7u1$\,Hi&#^ C9+?QX0bRG=*a9CJbiٟ} D#}U+|Ten9.Rol1b528$nMgšɗEtJ0E)u~+SL&!1AQaq @0P?;Y(>*Tʯ $s pF-JJ$4 Z`ZiHM|AD yb0[f)ۿG]W&c^gXTRW @HD)QiϑP1 +0FTv3^1*x4}!meKTUniK GQ&6L~]-d(%᎐qS!rU^qtQv P9@khKKwfD5j;{ޏMGuChV@1Qކa#6 r^~FFǒhbE;K5?YcB2G&ƃgWgPlR + + + + + Drupal EXIF Camera + 72 + 72 + Inch + Centered + Exif Version 2.1 + FlashPix Version 1.0 + Uncalibrated + N + 52, 30, 32.751 + E + 13, 22, 25.5432 + + + + + +C + + +    C   Y HNƋ`:Q&Y9nZl(-p㓟{'h\R@o3uv'N%Wi}cʭ0E-%+[}[/OI^fdp,Ć^_?}:ΞQ]gYh2Z]ՋEgDhf )ɋK0q,LGduV٭N#kOHW+Tס_D;|t!ΪNrvb=%qkMtQ#}vw]vHxzysl_i-vtv¯\.Ыj]]$:o,o>ma_[ +ȔJYԷ+ 콟C!yWK?܋2jU潲˅ˤQX' 6@03PH}jr5fKl\m5 qYqhc i8Ǻ7}Fzetc4 +B/,( %UR:sZy<ٖ.~ɟR`= ^%#?1ZwFh?;3Z#.K֓8#s;qWB/(]iCI =*@/.&{')Mt ZQ5Xn5P𛇤JfKʻ s}%eg"d!?iqIp kůhU衈>=քN6:~J )anjZYp{YgPMR\Q;5vA*>2m YC[~(SHl"ٵk֦%r- ?@Mi7D$lSC`Yt4t77:*8LW~isR7+C(lyԋLW')5UԷzNL4JEEX@=hI¤%p4Ji"$nqՑ@Á5|PT*AB )r)܂>XԬ='l>JSs=¡,|ULJ%! !G\uTgz j34SWƀ`*I@dc" ѧ:5C6Th2iԞ'iS! q(5m͑{fO}yM)Y>tJS] j hKvȓ +"RpVڑFb!Ѵf_sܝf 9¶{CN# <+ШܺEQ4VX'g: !"1A#2Qq 0@a45R$3Pc?QDq}(Oz4*TЎ7ړ +[$^֯BԜ9?ôu:ţ]jrUՆd0RU .%ar ԑ:z`nα eJM#)Dd1` 6OÃUY_v 쿟SumwjOrJ ⧢teN\ 7+4#!w5Cw)~'K{5{t%u$y ]b_J_a)`5Q.,sQ*.< V,$V2ǹ;OM8iM1~ ¤]'GϢr,C3tq6M u(mK(VR0i0,+ mRӪaar6_L[ҏXq0|0_*!5 Oݛʗ&;P>T]Sx=1 +ް)TÉLK >Z]BO/Ʈ($m7~sœFϟ4}i*̖AvK9^bӎiyRFt)EszU\Gp@d:|C !1"AQaq2B #@0RSTbrs$3P? 36S(2wT&kkw~r^Pt7a@$2a,Lˌ:ie #4dD Ǘlk߸oɓ|#nfBU++BBhD&Z}^0 +|Nr]5n / +Juro=U֏J$|PnjqJOخ5XeA{Tc"?ԕѬWU7DZUr?dJm*0ے(OeUv鷘KpG&YNjCvE)WD +i!xQƔn]dja! 90U'! o!T Y/+XS*$mH(AwJ)- БS&0haJkFQNN/Q0*dڽh-8ȴEBwy^ȓߨM'bArGtK!62xD3CJSRxԟl).v$") _Ma]&w\2n)w~9S?Q/6txGL#"^szjV6+Nix\GK&Y҂kw2ᇦՃj5L4f/P6ƧiRGth,S(J x (vjNDzyg_v_[^d _cbe +@Y'L)Lxȥ2ҐG,O}*iE_1Ӡe+Dޛ>3&~f6ML~k8T @"[@-ɥ1Yܸ TbhSJfbm 4}X.N,~MJ6%BV(]w#@Iԓ`Q%޵5b9K1"l +@3%|mZB`yzI}SgJ=0ًz,v4 g]I3 G$I$I$rI$I$*I$I$)Sl$I$Dm%I$Lm%5 so{BjNdrrI$$I$I$AI$I$I$I$I$O*!1AQaq@ 0P?ɘDvҍ;]qV~6XUnjX+yv-Rn^)_K JtA} x֖\.ƄAΞz)J6%7[a-I؁JUf"#zEl!bH7C@+9j u@-lUB:SY qĢ$_ 5*o*Oabj;PÀ-7]=@Dv58]1]37M76MN{U^p|-eאVL wPO:ߍhcC9Q5αu6{W;2U({RAVF +f&+`} #a8ڈA3paDPDUIn#WUb[/sBW"Ѽw4E7 F{kI%t\T],;V+-zik$J!eY|˭,Ɔ=uC33B6H,,̓h-ڇ8ne LYlҏV܋k9kS*qʀ,Ӏǘ LVLbm,c.νP5kEI b3`>?O1a[k + 8D4jz? @KA>_ sȤ`!/3*!1AQaq@ 0P? +n_oB5 xcRNRZ$O/'/=i7q:ڈD"L%<~v^X}eeC†u@Yj'Î^)ǙHʅ3.'e9tyCЏy0KąSTNRFR< m³&gNUybDֳP5ӝB149Nc]: 1_^FVĦtʭRU鹣z4S5}IB +pUȃǖbxGύӒys޼o +1cXaYvlrŇQY<{SI@7Kछ+8s484;E0]+Yy]3پXkСWl"oPd̴sʅԈjE]Rա ÏlFX-k e^bNed&81p,$ RhFdIl~.i7u1$\,Hi&#^ C9+?QX0bRG=*a9CJbiٟ} D#}U+|Ten9.Rol1b528$nMgšɗEtJ0E)u~+SL&!1AQaq @0P?;Y(>*Tʯ $s pF-JJ$4 Z`ZiHM|AD yb0[f)ۿG]W&c^gXTRW @HD)QiϑP1 +0FTv3^1*x4}!meKTUniK GQ&6L~]-d(%᎐qS!rU^qtQv P9@khKKwfD5j;{ޏMGuChV@1Qކa#6 r^~FFǒhbE;K5?YcB2G&ƃgWgPlRgetSession(); + $page = $session->getPage(); + $assert_session = $this->assertSession(); + + $this->doTestCreateMediaType($media_type_id, 'file', $provided_fields); + + // Create a custom field for the media type to store the MIME metadata + // attributes. + $fields = [ + 'field_string_mime' => 'string', + 'field_string_size' => '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[mime]", 'field_string_mime'); + $page->selectFieldOption("field_map[size]", 'field_string_size'); + $page->pressButton('Save'); + + // Create a media item. + $this->drupalGet("media/add/{$media_type_id}"); + $page->attachFileToField("files[{$source_field_id}_0]", \Drupal::root() . '/sites/README.txt'); + $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 that all fields are properly populated. + $media = Media::load(1); + $this->assertEquals('README.txt', $media->label()); + $this->assertEquals('text/plain', $media->get('field_string_mime')->value); + $this->assertEquals(filesize(\Drupal::root() . '/sites/README.txt'), $media->get('field_string_size')->value); + + // 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::root() . '/sites/README.txt'); + $assert_session->assertWaitOnAjaxRequest(); + $page->pressButton('Save and publish'); + $assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'text--plain.png'); + } + +} diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaSourceImageTest.php b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceImageTest.php new file mode 100644 index 0000000000..bfe1f83964 --- /dev/null +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceImageTest.php @@ -0,0 +1,94 @@ +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_mime' => 'string', + 'field_string_size' => 'string', + 'field_string_width' => 'string', + 'field_string_model' => '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[mime]", 'field_string_mime'); + $page->selectFieldOption("field_map[size]", 'field_string_size'); + $page->selectFieldOption("field_map[width]", 'field_string_width'); + $page->selectFieldOption("field_map[model]", 'field_string_model'); + $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/exif_example_1.jpeg'); + $assert_session->assertWaitOnAjaxRequest(); + $page->fillField("{$source_field_id}[0][alt]", 'EXIF Image Alt Text 1'); + $page->pressButton('Save and publish'); + + $assert_session->addressEquals('media/1'); + + // Make sure the thumbnail is displayed from uploaded image. + $assert_session->elementAttributeContains('css', '.image-style-thumbnail', 'src', 'exif_example_1.jpeg'); + + // Load the media and check that all fields are properly populated. + $media = Media::load(1); + $this->assertEquals('exif_example_1.jpeg', $media->label()); + $this->assertEquals('Drupal EXIF Camera 1', $media->get('field_string_model')->value); + $this->assertEquals('200', $media->get('field_string_width')->value); + $this->assertEquals('image/jpeg', $media->get('field_string_mime')->value); + $this->assertEquals(filesize(\Drupal::root() . '/core/modules/media/tests/fixtures/exif_example_1.jpeg'), $media->get('field_string_size')->value); + + + // Create a second media item. + $this->drupalGet("media/add/{$media_type_id}"); + $page->attachFileToField("files[{$source_field_id}_0]", \Drupal::root() . '/core/modules/media/tests/fixtures/exif_example_2.jpeg'); + $assert_session->assertWaitOnAjaxRequest(); + $page->fillField("{$source_field_id}[0][alt]", 'EXIF Image Alt Text 2'); + $page->pressButton('Save and publish'); + + // Load second media and ensure that statically cached EXIF data not reused. + $media_2 = Media::load(2); + $this->assertEquals('Drupal EXIF Camera 1', $media->get('field_string_model')->value); + $this->assertEquals('Drupal EXIF Camera 2', $media_2->get('field_string_model')->value); + } + +} diff --git a/core/modules/media/tests/src/FunctionalJavascript/MediaSourceTestBase.php b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceTestBase.php new file mode 100644 index 0000000000..0225824ee4 --- /dev/null +++ b/core/modules/media/tests/src/FunctionalJavascript/MediaSourceTestBase.php @@ -0,0 +1,126 @@ + $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); + } + +} diff --git a/core/profiles/standard/config/optional/core.entity_form_display.media.file.default.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.file.default.yml new file mode 100644 index 0000000000..15de2acd90 --- /dev/null +++ b/core/profiles/standard/config/optional/core.entity_form_display.media.file.default.yml @@ -0,0 +1,44 @@ +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: { } diff --git a/core/profiles/standard/config/optional/core.entity_form_display.media.image.default.yml b/core/profiles/standard/config/optional/core.entity_form_display.media.image.default.yml new file mode 100644 index 0000000000..a040f31b83 --- /dev/null +++ b/core/profiles/standard/config/optional/core.entity_form_display.media.image.default.yml @@ -0,0 +1,46 @@ +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: { } + uid: + type: entity_reference_autocomplete + weight: 5 + settings: + match_operator: CONTAINS + size: 60 + placeholder: '' + region: content + third_party_settings: { } +hidden: { } diff --git a/core/profiles/standard/config/optional/core.entity_view_display.media.file.default.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.file.default.yml new file mode 100644 index 0000000000..697f117c6e --- /dev/null +++ b/core/profiles/standard/config/optional/core.entity_view_display.media.file.default.yml @@ -0,0 +1,50 @@ +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: { } diff --git a/core/profiles/standard/config/optional/core.entity_view_display.media.image.default.yml b/core/profiles/standard/config/optional/core.entity_view_display.media.image.default.yml new file mode 100644 index 0000000000..e541d7f784 --- /dev/null +++ b/core/profiles/standard/config/optional/core.entity_view_display.media.image.default.yml @@ -0,0 +1,51 @@ +langcode: en +status: true +dependencies: + config: + - field.field.media.image.field_media_image + - image.style.thumbnail + - media.type.image + module: + - image + - user +id: media.image.default +targetEntityType: media +bundle: image +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_image: + label: above + settings: + image_style: '' + image_link: '' + third_party_settings: { } + type: image + 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: { } diff --git a/core/profiles/standard/config/optional/field.field.media.file.field_media_file.yml b/core/profiles/standard/config/optional/field.field.media.file.field_media_file.yml new file mode 100644 index 0000000000..5db61f0da2 --- /dev/null +++ b/core/profiles/standard/config/optional/field.field.media.file.field_media_file.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.media.field_media_file + - media.type.file + module: + - file + enforced: + module: + - media +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' + max_filesize: '' + handler: 'default:file' + handler_settings: { } + description_field: false +field_type: file diff --git a/core/profiles/standard/config/optional/field.field.media.image.field_media_image.yml b/core/profiles/standard/config/optional/field.field.media.image.field_media_image.yml new file mode 100644 index 0000000000..628c386df8 --- /dev/null +++ b/core/profiles/standard/config/optional/field.field.media.image.field_media_image.yml @@ -0,0 +1,40 @@ +langcode: en +status: true +dependencies: + config: + - field.storage.media.field_media_image + - media.type.image + module: + - image + enforced: + module: + - media +id: media.image.field_media_image +field_name: field_media_image +entity_type: media +bundle: image +label: field_media_image +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + file_extensions: 'png gif jpg jpeg' + 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]' + max_filesize: '' + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/core/profiles/standard/config/optional/field.storage.media.field_media_file.yml b/core/profiles/standard/config/optional/field.storage.media.field_media_file.yml new file mode 100644 index 0000000000..efe7954dc7 --- /dev/null +++ b/core/profiles/standard/config/optional/field.storage.media.field_media_file.yml @@ -0,0 +1,25 @@ +langcode: en +status: true +dependencies: + module: + - file + - media + enforced: + module: + - 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 diff --git a/core/profiles/standard/config/optional/field.storage.media.field_media_image.yml b/core/profiles/standard/config/optional/field.storage.media.field_media_image.yml new file mode 100644 index 0000000000..c2049c087c --- /dev/null +++ b/core/profiles/standard/config/optional/field.storage.media.field_media_image.yml @@ -0,0 +1,32 @@ +langcode: en +status: true +dependencies: + module: + - file + - image + - media + enforced: + module: + - 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: true +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/core/profiles/standard/config/optional/media.type.file.yml b/core/profiles/standard/config/optional/media.type.file.yml new file mode 100644 index 0000000000..4bd2f46ca4 --- /dev/null +++ b/core/profiles/standard/config/optional/media.type.file.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + module: + - media +id: file +label: File +description: 'Use the "File" media type for storing media locally.' +source: file +queue_thumbnail_downloads: false +new_revision: false +source_configuration: + source_field: field_media_file +field_map: { } diff --git a/core/profiles/standard/config/optional/media.type.image.yml b/core/profiles/standard/config/optional/media.type.image.yml new file mode 100644 index 0000000000..baea949668 --- /dev/null +++ b/core/profiles/standard/config/optional/media.type.image.yml @@ -0,0 +1,14 @@ +langcode: en +status: true +dependencies: + module: + - media +id: image +label: Image +description: 'Use the "Image" media type for storing image media locally.' +source: image +queue_thumbnail_downloads: false +new_revision: false +source_configuration: + source_field: field_media_image +field_map: { }