first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<link rel="alternate" href="photo_flickr.json"
type="application/json+oembed" title="Druplicon FTW!">
</head>
<body></body>
</html>

View File

@@ -0,0 +1,12 @@
{
"type": "photo",
"title": "Druplicon FTW!",
"width": "88",
"height": "100",
"url": "internal:\/core\/misc\/druplicon.png",
"thumbnail_url": "internal:\/core\/misc\/druplicon.png",
"thumbnail_width": 88,
"thumbnail_height": 100,
"provider_name": "Flickr",
"version": "1.0"
}

View File

@@ -0,0 +1,10 @@
{
"type": "photo",
"title": "Druplicon FTW!",
"url": "internal:\/core\/misc\/druplicon.png",
"thumbnail_url": "internal:\/core\/misc\/druplicon.png",
"thumbnail_width": 88,
"thumbnail_height": 100,
"provider_name": "Flickr",
"version": "1.0"
}

View File

@@ -0,0 +1,105 @@
[
{
"provider_name": "Vimeo",
"provider_url": "https:\/\/vimeo.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/vimeo.com\/*",
"https:\/\/vimeo.com\/album\/*\/video\/*",
"https:\/\/vimeo.com\/channels\/*\/*",
"https:\/\/vimeo.com\/groups\/*\/videos\/*",
"https:\/\/vimeo.com\/ondemand\/*\/*",
"https:\/\/player.vimeo.com\/video\/*"
],
"url": "https:\/\/vimeo.com\/api\/oembed.{format}",
"discovery": true
}
]
},
{
"provider_name": "Twitter",
"provider_url": "http:\/\/www.twitter.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/twitter.com\/*\/status\/*",
"https:\/\/*.twitter.com\/*\/status\/*"
],
"url": "https:\/\/publish.twitter.com\/oembed"
}
]
},
{
"provider_name": "Dailymotion",
"provider_url": "https:\/\/www.dailymotion.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/www.dailymotion.com\/video\/*"
],
"url": "https:\/\/www.dailymotion.com\/services\/oembed",
"discovery": true
}
]
},
{
"provider_name": "Flickr",
"provider_url": "http:\/\/www.flickr.com\/",
"endpoints": [
{
"schemes": [
"http:\/\/*.flickr.com\/photos\/*",
"http:\/\/flic.kr\/p\/*"
],
"url": "http:\/\/www.flickr.com\/services\/oembed\/",
"discovery": true
}
]
},
{
"provider_name": "YouTube",
"provider_url": "https://www.youtube.com/",
"endpoints": [
{
"schemes": [
"https://*.youtube.com/watch*",
"https://*.youtube.com/v/*\"",
"https://youtu.be/*"
],
"url": "https://www.youtube.com/oembed",
"discovery": true
}
]
},
{
"provider_name": "Facebook",
"provider_url": "https:\/\/www.facebook.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/www.facebook.com\/*\/posts\/*",
"https:\/\/www.facebook.com\/photos\/*",
"https:\/\/www.facebook.com\/*\/photos\/*",
"https:\/\/www.facebook.com\/photo.php*",
"https:\/\/www.facebook.com\/photo.php",
"https:\/\/www.facebook.com\/*\/activity\/*",
"https:\/\/www.facebook.com\/permalink.php",
"https:\/\/www.facebook.com\/media\/set?set=*",
"https:\/\/www.facebook.com\/questions\/*",
"https:\/\/www.facebook.com\/notes\/*\/*\/*"
],
"url": "https:\/\/www.facebook.com\/plugins\/post\/oembed.json",
"discovery": true
},
{
"schemes": [
"https:\/\/www.facebook.com\/*\/videos\/*",
"https:\/\/www.facebook.com\/video.php"
],
"url": "https:\/\/www.facebook.com\/plugins\/video\/oembed.json",
"discovery": true
}
]
}
]

View File

@@ -0,0 +1,13 @@
{
"url": "https:\/\/twitter.com\/drupaldevdays\/status\/935643039741202432",
"author_name": "Drupal Dev Days",
"author_url": "https:\/\/twitter.com\/drupaldevdays",
"html": "<h1>Twitter works!</h1>",
"width": 550,
"height": null,
"type": "rich",
"cache_age": "3153600000",
"provider_name": "Twitter",
"provider_url": "https:\/\/twitter.com",
"version": "1.0"
}

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<link rel="alternate" href="video_dailymotion.xml"
type="application/xml+oembed" title="#d8rules - Support the Rules module for Drupal 8">
</head>
<body></body>
</html>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<oembed>
<type>video</type>
<version>1.0</version>
<title>#d8rules - Support the Rules module for Drupal 8</title>
<https/>
<author_name>Leepchic</author_name>
<author_url>https://www.dailymotion.com/leepchic</author_url>
<provider_name>Dailymotion</provider_name>
<provider_url>https://www.dailymotion.com</provider_url>
<width>610</width>
<height>343</height>
<html><h1>Dailymotion works!</h1>
</html>
<!-- The thumbnail URL does not contain a file extension, so we use
this to test the oEmbed source plugin's thumbnail handling;
see \Drupal\Tests\media\FunctionalJavascript\MediaSourceOEmbedVideoTest. -->
<thumbnail_url>internal:/media_test_oembed/thumbnail</thumbnail_url>
<thumbnail_width>88</thumbnail_width>
<thumbnail_height>100</thumbnail_height>
</oembed>

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<link rel="alternate" href="video_vimeo-no-title.json"
type="application/json+oembed" title="">
</head>
<body></body>
</html>

View File

@@ -0,0 +1,16 @@
{
"type": "video",
"version": "1.0",
"provider_name": "Vimeo",
"provider_url": "https:\/\/vimeo.com\/",
"title": "",
"author_name": "Tendenci - The Open Source AMS",
"author_url": "https:\/\/vimeo.com\/schipul",
"html": "<iframe width=\"480\">Vimeo works!</iframe>",
"width": 480,
"height": 360,
"description": "Special thanks to Tendenci, formerly Schipul for sponsoring this video with training, equipment and time. The open source way. All creative however was self directed by the individuals - A. Hughes (www.schipul.com\/ahughes) featuring QCait (www.schipul.com\/qcait) - Hands On Drupal\n\nDrupal is a free software package that allows an individual or a community of users to easily publish, manage and organize a wide variety of content on a website.\n\nNeed a little Drupal help or just want to geek out with us? Visit our www.schipul.com\/drupal for more info - we'd love to connect!\n\nGo here for Drupal Common Terms and Suggested Modules : http:\/\/schipul.com\/en\/helpfiles\/v\/229",
"thumbnail_url": "internal:\/core\/misc\/druplicon.png",
"thumbnail_width": 295,
"thumbnail_height": 221
}

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<link rel="alternate" href="video_vimeo-resized.json"
type="application/json+oembed" title="Drupal Rap Video - Schipulcon09">
</head>
<body></body>
</html>

View File

@@ -0,0 +1,16 @@
{
"type": "video",
"version": "1.0",
"provider_name": "Vimeo",
"provider_url": "https:\/\/vimeo.com\/",
"title": "Drupal Rap Video - Schipulcon09",
"author_name": "Tendenci - The Open Source AMS",
"author_url": "https:\/\/vimeo.com\/schipul",
"html": "<iframe width=\"100\">By the power of Grayskull, Vimeo works!</iframe>",
"width": 100,
"height": 67,
"description": "Special thanks to Tendenci, formerly Schipul for sponsoring this video with training, equipment and time. The open source way. All creative however was self directed by the individuals - A. Hughes (www.schipul.com\/ahughes) featuring QCait (www.schipul.com\/qcait) - Hands On Drupal\n\nDrupal is a free software package that allows an individual or a community of users to easily publish, manage and organize a wide variety of content on a website.\n\nNeed a little Drupal help or just want to geek out with us? Visit our www.schipul.com\/drupal for more info - we'd love to connect!\n\nGo here for Drupal Common Terms and Suggested Modules : http:\/\/schipul.com\/en\/helpfiles\/v\/229",
"thumbnail_url": "internal:\/core\/misc\/druplicon.png",
"thumbnail_width": 100,
"thumbnail_height": 67
}

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="de">
<head>
<link rel="alternate" href="video_vimeo.json"
type="application/json+oembed" title="Drupal Rap Video - Schipulcon09">
</head>
<body></body>
</html>

View File

@@ -0,0 +1,16 @@
{
"type": "video",
"version": "1.0",
"provider_name": "Vimeo",
"provider_url": "https:\/\/vimeo.com\/",
"title": "Drupal Rap Video - Schipulcon09",
"author_name": "Tendenci - The Open Source AMS",
"author_url": "https:\/\/vimeo.com\/schipul",
"html": "<iframe width=\"480\">Vimeo works!</iframe>",
"width": 480,
"height": 360,
"description": "Special thanks to Tendenci, formerly Schipul for sponsoring this video with training, equipment and time. The open source way. All creative however was self directed by the individuals - A. Hughes (www.schipul.com\/ahughes) featuring QCait (www.schipul.com\/qcait) - Hands On Drupal\n\nDrupal is a free software package that allows an individual or a community of users to easily publish, manage and organize a wide variety of content on a website.\n\nNeed a little Drupal help or just want to geek out with us? Visit our www.schipul.com\/drupal for more info - we'd love to connect!\n\nGo here for Drupal Common Terms and Suggested Modules : http:\/\/schipul.com\/en\/helpfiles\/v\/229",
"thumbnail_url": "internal:\/core\/misc\/druplicon.png",
"thumbnail_width": 295,
"thumbnail_height": 221
}

View File

@@ -0,0 +1,8 @@
<!DOCTYPE html>
<html lang="nl">
<head>
<link rel="alternate" href="video_youtube.json"
type="application/json+oembed" title="Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)">
</head>
<body></body>
</html>

View File

@@ -0,0 +1,15 @@
{
"type": "video",
"version": "1.0",
"provider_name": "YouTube",
"provider_url": "https:\/\/www.youtube.com\/",
"title": "Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)",
"author_name": "Mirakolous",
"author_url": "https:\/\/www.youtube.com\/user\/Mirakolous",
"html": "<h1>YouTube works!</h1>",
"width": 480,
"height": 270,
"thumbnail_url": "internal:\/core\/misc\/druplicon.png",
"thumbnail_width": 480,
"thumbnail_height": 360
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* @file
* Test oembed update by adding an oembed field display without config.
*/
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
// Add a remote video media type.
$media_type = [];
$media_type['langcode'] = 'en';
$media_type['status'] = TRUE;
$media_type['dependencies'] = [];
$media_type['id'] = 'remote_video';
$media_type['uuid'] = 'c86d3b5c-b788-49ab-a06c-257895e47731';
$media_type['label'] = 'Remote video';
$media_type['description'] = 'A remotely hosted video from YouTube or Vimeo.';
$media_type['source'] = 'oembed:video';
$media_type['queue_thumbnail_downloads'] = FALSE;
$media_type['new_revision'] = TRUE;
$media_type['source_configuration'] = [
'source_field' => 'field_media_oembed_video',
'thumbnails_directory' => 'public://oembed_thumbnails/[date:custom:Y-m]',
'providers' => [
'YouTube',
'Vimeo',
],
];
$media_type['field_map'] = [
'title' => 'name',
];
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'media.type.remote_video',
'data' => serialize($media_type),
])
->execute();
// Add a remote video view display.
$display = [];
$display['langcode'] = 'en';
$display['status'] = TRUE;
$display['dependencies']['config'][] = 'media.type.remote_video';
$display['dependencies']['module'][] = 'media';
$display['id'] = 'media.remote_video.default';
$display['uuid'] = 'ff13f7fb-d493-4d45-a56d-31a1a0762df7';
$display['targetEntityType'] = 'media';
$display['bundle'] = 'remote_video';
$display['mode'] = 'default';
$display['content']['field_media_oembed_video'] = [
'type' => 'oembed',
'label' => 'hidden',
'settings' => [
'max_width' => 0,
'max_height' => 0,
],
'third_party_settings' => [],
'weight' => 0,
'region' => 'content',
];
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'core.entity_view_display.media.remote_video.default',
'data' => serialize($display),
])
->execute();

View File

@@ -0,0 +1,70 @@
<?php
// phpcs:ignoreFile
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
// Set the schema version.
$connection->merge('key_value')
->fields([
'value' => 'i:8700;',
'name' => 'media',
'collection' => 'system.schema',
])
->condition('collection', 'system.schema')
->condition('name', 'media')
->execute();
// Update core.extension.
$extensions = $connection->select('config')
->fields('config', ['data'])
->condition('collection', '')
->condition('name', 'core.extension')
->execute()
->fetchField();
$extensions = unserialize($extensions);
$extensions['module']['media'] = 0;
$connection->update('config')
->fields(['data' => serialize($extensions)])
->condition('collection', '')
->condition('name', 'core.extension')
->execute();
// Add all media_removed_post_updates() as existing updates.
require_once __DIR__ . '/../../../../media/media.post_update.php';
$existing_updates = $connection->select('key_value')
->fields('key_value', ['value'])
->condition('collection', 'post_update')
->condition('name', 'existing_updates')
->execute()
->fetchField();
$existing_updates = unserialize($existing_updates);
$existing_updates = array_merge(
$existing_updates,
array_keys(media_removed_post_updates())
);
$connection->update('key_value')
->fields(['value' => serialize($existing_updates)])
->condition('collection', 'post_update')
->condition('name', 'existing_updates')
->execute();
// Create media.settings.
$connection->insert('config')
->fields([
'collection',
'name',
'data',
])
->values([
'collection' => '',
'name' => 'media.settings',
'data' => serialize([
'icon_base_uri' => 'public://media-icons/generic',
'iframe_domain' => '',
'oembed_providers_url' => 'https://oembed.com/providers.json',
'standalone_url' => FALSE,
]),
])
->execute();

View File

@@ -0,0 +1,12 @@
name: Media Embed text editor plugin test
description: 'Provides functionality to test embedding media items in text editors.'
type: module
package: Testing
# version: VERSION
dependencies:
- drupal:media
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,33 @@
<?php
/**
* @file
* Helper module for the Media Embed text editor plugin tests.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Implements hook_entity_view_alter().
*/
function media_test_embed_entity_view_alter(&$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
$build['#attributes']['data-media-embed-test-active-theme'] = \Drupal::theme()->getActiveTheme()->getName();
$build['#attributes']['data-media-embed-test-view-mode'] = $display->getMode();
}
/**
* Implements hook_preprocess_HOOK().
*/
function media_test_embed_preprocess_media_embed_error(&$variables) {
$variables['attributes']['class'][] = 'this-error-message-is-themeable';
}
/**
* Implements hook_entity_access().
*/
function media_test_embed_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
return AccessResult::neutral()->addCacheTags(['_media_test_embed_filter_access:' . $entity->getEntityTypeId() . ':' . $entity->id()]);
}

View File

@@ -0,0 +1,5 @@
services:
_defaults:
autoconfigure: true
media_test_embed.route_subscriber:
class: Drupal\media_test_embed\Routing\RouteSubscriber

View File

@@ -0,0 +1,25 @@
<?php
namespace Drupal\media_test_embed\Controller;
use Drupal\filter\FilterFormatInterface;
use Drupal\media\Controller\MediaFilterController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Controller to allow testing of error handling of Media Embed in text editors.
*/
class TestMediaFilterController extends MediaFilterController {
/**
* {@inheritdoc}
*/
public function preview(Request $request, FilterFormatInterface $filter_format) {
if (\Drupal::state()->get('test_media_filter_controller_throw_error', FALSE)) {
throw new NotFoundHttpException();
}
return parent::preview($request, $filter_format);
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace Drupal\media_test_embed\Routing;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;
/**
* Listens to the dynamic route events.
*/
class RouteSubscriber extends RouteSubscriberBase {
/**
* {@inheritdoc}
*/
public function alterRoutes(RouteCollection $collection) {
if ($route = $collection->get('media.filter.preview')) {
$route->setDefault('_controller', '\Drupal\media_test_embed\Controller\TestMediaFilterController::preview');
}
}
}

View File

@@ -0,0 +1,5 @@
/**
* This is an empty file by design.
* @see \Drupal\Tests\media\Kernel\OEmbedIframeControllerTest::testResourcePassedToPreprocess()
* @see media_test_oembed_preprocess_media_oembed_iframe()
*/

View File

@@ -0,0 +1,12 @@
name: Media oEmbed test
description: 'Provides functionality to mimic an oEmbed provider.'
type: module
package: Testing
# version: VERSION
dependencies:
- drupal:media
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,5 @@
frame:
version: VERSION
css:
component:
css/test.css: { preprocess: false, minified: true }

View File

@@ -0,0 +1,29 @@
<?php
/**
* @file
* Helper module for the Media oEmbed tests.
*/
use Drupal\media\OEmbed\Provider;
/**
* Implements hook_preprocess_media_oembed_iframe().
*/
function media_test_oembed_preprocess_media_oembed_iframe(array &$variables) {
if ($variables['resource']->getProvider()->getName() === 'YouTube') {
$variables['media'] = str_replace('?feature=oembed', '?feature=oembed&pasta=rigatoni', (string) $variables['media']);
}
// @see \Drupal\Tests\media\Kernel\OEmbedIframeControllerTest
$variables['#attached']['library'][] = 'media_test_oembed/frame';
$variables['#cache']['tags'][] = 'yo_there';
}
/**
* Implements hook_oembed_resource_url_alter().
*/
function media_test_oembed_oembed_resource_url_alter(array &$parsed_url, Provider $provider) {
if ($provider->getName() === 'Vimeo') {
$parsed_url['query']['altered'] = 1;
}
}

View File

@@ -0,0 +1,12 @@
media_test_oembed.resource.get:
path: '/media_test_oembed/resource'
defaults:
_controller: '\Drupal\media_test_oembed\Controller\ResourceController::get'
requirements:
_access: 'TRUE'
media_test_oembed.resource.thumbnail:
path: '/media_test_oembed/thumbnail'
defaults:
_controller: '\Drupal\media_test_oembed\Controller\ResourceController::getThumbnailWithNoExtension'
requirements:
_access: 'TRUE'

View File

@@ -0,0 +1,74 @@
<?php
namespace Drupal\media_test_oembed\Controller;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* Test controller returning oEmbed resources from Media's test fixtures.
*/
class ResourceController {
/**
* Creates an oEmbed resource response.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request.
*
* @return \Symfony\Component\HttpFoundation\Response
* The oEmbed resource response.
*/
public function get(Request $request) {
$asset_url = $request->query->get('url');
$resource = \Drupal::keyValue('media_test_oembed')->get($asset_url);
if ($resource === 404) {
$response = new Response('Not Found', 404);
}
else {
$content = file_get_contents($resource);
$response = new Response($content);
$response->headers->set('Content-Type', 'application/' . pathinfo($resource, PATHINFO_EXTENSION));
}
return $response;
}
/**
* Returns an example thumbnail file without an extension.
*
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
* The response.
*/
public function getThumbnailWithNoExtension() {
$response = new BinaryFileResponse('core/misc/druplicon.png');
$response->headers->set('Content-Type', 'image/png');
return $response;
}
/**
* Maps an asset URL to a local fixture representing its oEmbed resource.
*
* @param string $asset_url
* The asset URL.
* @param string $resource_path
* The path of the oEmbed resource representing the asset.
*/
public static function setResourceUrl($asset_url, $resource_path) {
\Drupal::keyValue('media_test_oembed')->set($asset_url, $resource_path);
}
/**
* Maps an asset URL to a 404 response.
*
* @param string $asset_url
* The asset URL.
*/
public static function setResource404($asset_url) {
\Drupal::keyValue('media_test_oembed')->set($asset_url, 404);
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Drupal\media_test_oembed;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
/**
* Replaces oEmbed-related media services with testing versions.
*/
class MediaTestOembedServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
parent::alter($container);
$container->getDefinition('media.oembed.provider_repository')
->setClass(ProviderRepository::class);
$container->getDefinition('media.oembed.url_resolver')
->setClass(UrlResolver::class);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Drupal\media_test_oembed;
use Drupal\media\OEmbed\Provider;
use Drupal\media\OEmbed\ProviderRepository as BaseProviderRepository;
/**
* Overrides the oEmbed provider repository service for testing purposes.
*
* This service does not use caching at all, and will always try to retrieve
* provider data from state before calling the parent methods.
*/
class ProviderRepository extends BaseProviderRepository {
/**
* {@inheritdoc}
*/
public function getAll() {
return \Drupal::state()->get(static::class) ?: parent::getAll();
}
/**
* {@inheritdoc}
*/
public function get($provider_name) {
$providers = \Drupal::state()->get(static::class, []);
if (isset($providers[$provider_name])) {
return $providers[$provider_name];
}
return parent::get($provider_name);
}
/**
* Stores an oEmbed provider value object in state.
*
* @param \Drupal\media\OEmbed\Provider $provider
* The provider to store.
*/
public function setProvider(Provider $provider) {
$providers = \Drupal::state()->get(static::class, []);
$name = $provider->getName();
$providers[$name] = $provider;
\Drupal::state()->set(static::class, $providers);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Drupal\media_test_oembed;
use Drupal\media\OEmbed\UrlResolver as BaseUrlResolver;
/**
* Overrides the oEmbed URL resolver service for testing purposes.
*/
class UrlResolver extends BaseUrlResolver {
/**
* Sets the endpoint URL for an oEmbed resource URL.
*
* @param string $url
* The resource URL.
* @param string $endpoint_url
* The endpoint URL.
*/
public static function setEndpointUrl($url, $endpoint_url) {
$urls = \Drupal::state()->get(static::class, []);
$urls[$url] = $endpoint_url;
\Drupal::state()->set(static::class, $urls);
}
/**
* {@inheritdoc}
*/
public function getResourceUrl($url, $max_width = NULL, $max_height = NULL) {
$urls = \Drupal::state()->get(static::class, []);
if (isset($urls[$url])) {
return $urls[$url];
}
return parent::getResourceUrl($url, $max_width, $max_height);
}
}

View File

@@ -0,0 +1,10 @@
name: 'Test media source'
type: module
description: 'Provides test media source to test configuration forms.'
package: Testing
# version: VERSION
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,26 @@
<?php
namespace Drupal\media_test_source\Plugin\Validation\Constraint;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Validation\Attribute\Constraint;
use Symfony\Component\Validator\Constraint as SymfonyConstraint;
/**
* A media test constraint.
*/
#[Constraint(
id: 'MediaTestConstraint',
label: new TranslatableMarkup('Media constraint for test purposes.', [], ['context' => 'Validation']),
type: ['entity', 'string']
)]
class MediaTestConstraint extends SymfonyConstraint {
/**
* The default violation message.
*
* @var string
*/
public $message = 'Inappropriate text.';
}

View File

@@ -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 (!str_contains($string_to_test, 'love Drupal')) {
$this->context->addViolation($constraint->message);
}
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Drupal\media_test_source\Plugin\media\Source;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\media\Attribute\MediaSource;
use Drupal\media\MediaInterface;
use Drupal\media\MediaSourceBase;
/**
* Provides test media source.
*/
#[MediaSource(
id: "test",
label: new TranslatableMarkup("Test source"),
description: new TranslatableMarkup("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;
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Drupal\media_test_source\Plugin\media\Source;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\media\Attribute\MediaSource;
use Drupal\media\MediaTypeInterface;
/**
* Provides test media source.
*/
#[MediaSource(
id: "test_different_displays",
label: new TranslatableMarkup("Test source with different displays"),
description: new TranslatableMarkup("Test source with different displays."),
allowed_field_types: ["entity_reference"]
)]
class TestDifferentDisplays extends Test {
/**
* {@inheritdoc}
*/
public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) {
parent::prepareViewDisplay($type, $display);
$source_name = $this->getSourceFieldDefinition($type)->getName();
$source_component = $display->getComponent($source_name) ?: [];
$source_component['type'] = 'entity_reference_entity_id';
$display->setComponent($source_name, $source_component);
}
/**
* {@inheritdoc}
*/
public function prepareFormDisplay(MediaTypeInterface $type, EntityFormDisplayInterface $display) {
parent::prepareFormDisplay($type, $display);
$source_name = $this->getSourceFieldDefinition($type)->getName();
$source_component = $display->getComponent($source_name) ?: [];
$source_component['type'] = 'entity_reference_autocomplete_tags';
$display->setComponent($source_name, $source_component);
}
/**
* {@inheritdoc}
*/
protected function getSourceFieldName() {
return 'field_media_different_display';
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Drupal\media_test_source\Plugin\media\Source;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\media\Attribute\MediaSource;
use Drupal\media\MediaInterface;
/**
* Provides test media source.
*/
#[MediaSource(
id: "test_translation",
label: new TranslatableMarkup("Test source with translations"),
description: new TranslatableMarkup("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);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Drupal\media_test_source\Plugin\media\Source;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\media\Attribute\MediaSource;
use Drupal\media\MediaSourceEntityConstraintsInterface;
use Drupal\media\MediaSourceFieldConstraintsInterface;
/**
* Provides generic media type.
*/
#[MediaSource(
id: "test_constraints",
label: new TranslatableMarkup("Test source with constraints"),
description: new TranslatableMarkup("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', []);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Drupal\media_test_source\Plugin\media\Source;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\media\Attribute\MediaSource;
use Drupal\media\MediaTypeInterface;
/**
* Provides test media source.
*/
#[MediaSource(
id: "test_hidden_source_field",
label: new TranslatableMarkup("Test source with hidden source field"),
description: new TranslatableMarkup("Test media source with hidden source field."),
allowed_field_types: ["string"],
)]
class TestWithHiddenSourceField extends Test {
/**
* {@inheritdoc}
*/
public function prepareViewDisplay(MediaTypeInterface $type, EntityViewDisplayInterface $display) {
$display->removeComponent($this->getSourceFieldDefinition($type)->getName());
}
/**
* {@inheritdoc}
*/
public function prepareFormDisplay(MediaTypeInterface $type, EntityFormDisplayInterface $display) {
$display->removeComponent($this->getSourceFieldDefinition($type)->getName());
}
/**
* {@inheritdoc}
*/
protected function getSourceFieldName() {
return 'field_media_hidden';
}
}

View File

@@ -0,0 +1,13 @@
name: 'Media test type'
type: module
description: 'Provides test type for a media item.'
package: Testing
# version: VERSION
dependencies:
- drupal:media
- drupal:media_test_source
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,13 @@
name: 'Media test views'
type: module
description: 'Provides default views for views media tests.'
package: Testing
# version: VERSION
dependencies:
- drupal:media
- drupal:views
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,388 @@
langcode: en
status: true
dependencies:
module:
- media
- user
id: test_media_revision_uid
label: 'Test Media revision uid'
module: views
description: ''
tag: ''
base_table: media_field_data
base_field: mid
display:
default:
display_plugin: default
id: default
display_title: Default
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
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: Apply
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: none
options:
offset: 0
style:
type: default
options:
grouping: { }
row_class: ''
default_row_class: true
uses_fields: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
fields:
mid:
id: mid
table: media_field_data
field: mid
relationship: none
group_type: group
admin_label: ''
label: ''
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: false
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: number_integer
settings:
thousand_separator: ''
prefix_suffix: false
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: mid
plugin_id: field
vid:
id: vid
table: media_field_data
field: vid
relationship: none
group_type: group
admin_label: ''
label: ''
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: false
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: number_integer
settings:
thousand_separator: ''
prefix_suffix: false
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: vid
plugin_id: field
uid:
id: uid
table: media_field_data
field: uid
relationship: none
group_type: group
admin_label: ''
label: ''
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: false
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: uid
plugin_id: field
revision_user:
id: revision_user
table: media_revision
field: revision_user
relationship: none
group_type: group
admin_label: ''
label: ''
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: false
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: revision_user
plugin_id: field
filters:
revision_user:
id: revision_user
table: media_revision
field: revision_user
relationship: none
group_type: group
admin_label: ''
operator: in
value: { }
group: 1
exposed: true
expose:
operator_id: revision_user_op
label: 'Revision user'
description: ''
use_operator: false
operator: revision_user_op
operator_limit_selection: false
operator_list: { }
identifier: revision_user
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: revision_user
plugin_id: user_name
sorts: { }
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
filter_groups:
operator: AND
groups: { }
cache_metadata:
max-age: -1
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url
- user.permissions
tags: { }

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\FieldFormatter;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Drupal\Tests\media\Functional\MediaFunctionalTestBase;
use Drupal\Tests\TestFileCreationTrait;
/**
* @covers \Drupal\media\Plugin\Field\FieldFormatter\MediaThumbnailFormatter
*
* @group media
*/
class MediaThumbnailFormatterTest extends MediaFunctionalTestBase {
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the media thumbnail field formatter.
*/
public function testRender(): void {
$this->drupalLogin($this->adminUser);
/** @var \Drupal\node\NodeStorage $node_storage */
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
// Create an image media type for testing the formatter.
$this->createMediaType('image', ['id' => 'image']);
// Create an article content type.
$this->drupalCreateContentType([
'type' => 'article',
'name' => 'Article',
]);
// Creates an entity reference field for media.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_media_reference',
'type' => 'entity_reference',
'entity_type' => 'node',
'cardinality' => 1,
'settings' => [
'target_type' => 'media',
],
]);
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'article',
'label' => 'Reference media',
'translatable' => FALSE,
])->save();
// Alter the form display.
$this->container->get('entity_display.repository')
->getFormDisplay('node', 'article')
->setComponent('field_media_reference', [
'type' => 'entity_reference_autocomplete',
])
->save();
// Change the image thumbnail to point into the media.
$this->changeMediaReferenceFieldLinkType('media');
// Create and upload a file to the media.
$file = File::create([
'uri' => current($this->getTestFiles('image'))->uri,
]);
$file->save();
$mediaImage = Media::create([
'bundle' => 'image',
'name' => 'Test image',
'field_media_image' => $file->id(),
]);
$mediaImage->save();
// Save the article node.
$title = $this->randomMachineName();
$edit = [
'title[0][value]' => $title,
];
$edit['field_media_reference[0][target_id]'] = $mediaImage->getName();
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
// Validate the image being loaded with the media reference.
$this->assertSession()->responseContains('<a href="' . $mediaImage->toUrl('edit-form')->toString());
// Retrieve the created node.
$node = $this->drupalGetNodeByTitle($title);
$nid = $node->id();
// Change the image thumbnail to point into the content node.
$this->changeMediaReferenceFieldLinkType('content');
$node_storage->resetCache([$nid]);
$this->drupalGet('node/' . $nid);
// Validate image being loaded with the content on the link.
$this->assertSession()->responseContains('<a href="' . $node->toUrl()->toString());
$this->assertSession()->responseContains('loading="eager"');
}
/**
* Helper function to change field display.
*
* @param string $type
* Image link type.
*/
private function changeMediaReferenceFieldLinkType(string $type): void {
// Change the display to use the media thumbnail formatter with image link.
$this->container->get('entity_display.repository')
->getViewDisplay('node', 'article', 'default')
->setComponent('field_media_reference', [
'type' => 'media_thumbnail',
'settings' => [
'image_link' => $type,
'image_style' => '',
'image_loading' => ['attribute' => 'eager'],
],
])
->save();
}
}

View File

@@ -0,0 +1,246 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\FieldFormatter;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\media\Entity\Media;
use Drupal\media_test_oembed\Controller\ResourceController;
use Drupal\media_test_oembed\UrlResolver;
use Drupal\Tests\media\Functional\MediaFunctionalTestBase;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
// cspell:ignore Schipulcon
/**
* @covers \Drupal\media\Plugin\Field\FieldFormatter\OEmbedFormatter
*
* @group media
* @group #slow
*/
class OEmbedFormatterTest extends MediaFunctionalTestBase {
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'field_ui',
'link',
'media_test_oembed',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->lockHttpClientToFixtures();
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
}
/**
* Data provider for testRender().
*
* @see ::testRender()
*
* @return array
*/
public static function providerRender() {
return [
'Vimeo video' => [
'https://vimeo.com/7073899',
'video_vimeo.json',
[],
[
'iframe' => [
'src' => '/media/oembed?url=https%3A//vimeo.com/7073899',
'width' => '480',
'height' => '360',
'title' => 'Drupal Rap Video - Schipulcon09',
'loading' => 'lazy',
// cSpell:disable-next-line
'allowtransparency' => NULL,
'frameborder' => NULL,
],
],
'self_closing' => TRUE,
],
'Vimeo video, resized' => [
'https://vimeo.com/7073899',
'video_vimeo-resized.json',
['max_width' => '100', 'max_height' => '100'],
[
'iframe' => [
'src' => '/media/oembed?url=https%3A//vimeo.com/7073899&max_width=100&max_height=100',
'width' => '100',
'height' => '67',
'title' => 'Drupal Rap Video - Schipulcon09',
'loading' => 'lazy',
],
],
'self_closing' => TRUE,
],
'Vimeo video, no title' => [
'https://vimeo.com/7073899',
'video_vimeo-no-title.json',
[],
[
'iframe' => [
'src' => '/media/oembed?url=https%3A//vimeo.com/7073899',
'width' => '480',
'height' => '360',
'title' => NULL,
'loading' => 'lazy',
],
],
'self_closing' => TRUE,
],
'tweet' => [
'https://twitter.com/drupaldevdays/status/935643039741202432',
'rich_twitter.json',
[
// The tweet resource does not specify a height, so the formatter
// should default to the configured maximum height.
'max_height' => 360,
'loading' => ['attribute' => 'eager'],
],
[
'iframe' => [
'src' => '/media/oembed?url=https%3A//twitter.com/drupaldevdays/status/935643039741202432',
'width' => '550',
'height' => '360',
'loading' => 'eager',
],
],
'self_closing' => TRUE,
],
'Flickr photo' => [
'https://www.flickr.com/photos/amazeelabs/26497866357',
'photo_flickr.json',
[],
[
'img' => [
'src' => '/core/misc/druplicon.png',
'width' => '88',
'height' => '100',
'loading' => 'lazy',
],
],
'self_closing' => FALSE,
],
'Flickr photo (no dimensions)' => [
'https://www.flickr.com/photos/amazeelabs/26497866357',
'photo_flickr_no_dimensions.json',
[],
[
'img' => [
'src' => '/core/misc/druplicon.png',
'loading' => 'lazy',
],
],
'self_closing' => FALSE,
],
];
}
/**
* Tests that oEmbed media types' display can be configured correctly.
*/
public function testDisplayConfiguration(): void {
$account = $this->drupalCreateUser(['administer media display']);
$this->drupalLogin($account);
$media_type = $this->createMediaType('oembed:video');
$this->drupalGet('/admin/structure/media/manage/' . $media_type->id() . '/display');
$assert = $this->assertSession();
$assert->statusCodeEquals(200);
// Test that the formatter doesn't try to check applicability for fields
// which do not have a specific target bundle.
// @see https://www.drupal.org/project/drupal/issues/2976795.
$assert->pageTextNotContains('Can only flip STRING and INTEGER values!');
}
/**
* Tests the oEmbed field formatter.
*
* @param string $url
* The canonical URL of the media asset to test.
* @param string $resource_url
* The oEmbed resource URL of the media asset to test.
* @param array $formatter_settings
* Settings for the oEmbed field formatter.
* @param array $selectors
* An array of arrays. Each key is a CSS selector targeting an element in
* the rendered output, and each value is an array of attributes, keyed by
* name, that the element is expected to have.
* @param bool $self_closing
* Indicator if the HTML element is self closing i.e. <p/> vs <p></p>.
*
* @dataProvider providerRender
*/
public function testRender($url, $resource_url, array $formatter_settings, array $selectors, bool $self_closing): void {
$account = $this->drupalCreateUser(['view media']);
$this->drupalLogin($account);
$media_type = $this->createMediaType('oembed:video');
$source = $media_type->getSource();
$source_field = $source->getSourceFieldDefinition($media_type);
EntityViewDisplay::create([
'targetEntityType' => 'media',
'bundle' => $media_type->id(),
'mode' => 'full',
'status' => TRUE,
])->removeComponent('thumbnail')
->setComponent($source_field->getName(), [
'type' => 'oembed',
'settings' => $formatter_settings,
])
->save();
$this->hijackProviderEndpoints();
ResourceController::setResourceUrl($url, $this->getFixturesDirectory() . '/' . $resource_url);
UrlResolver::setEndpointUrl($url, $resource_url);
$entity = Media::create([
'bundle' => $media_type->id(),
$source_field->getName() => $url,
]);
$entity->save();
$this->drupalGet($entity->toUrl());
$assert = $this->assertSession();
$assert->statusCodeEquals(200);
foreach ($selectors as $selector => $attributes) {
$element = $assert->elementExists('css', $selector);
if ($self_closing) {
self::assertStringContainsString("</$selector", $element->getParent()->getHtml());
}
foreach ($attributes as $attribute => $value) {
if (isset($value)) {
$this->assertStringContainsString($value, $element->getAttribute($attribute));
}
else {
$this->assertFalse($element->hasAttribute($attribute));
}
}
}
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\FieldFormatter;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
/**
* Tests eager-load upgrade path.
*
* @group media
* @group legacy
* @group #slow
*/
class OembedUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Because the test manually installs media module, the entity type config
// must be manually installed similar to kernel tests.
$entity_type_manager = \Drupal::entityTypeManager();
$media = $entity_type_manager->getDefinition('media');
\Drupal::service('entity_type.listener')->onEntityTypeCreate($media);
$media_type = $entity_type_manager->getDefinition('media_type');
\Drupal::service('entity_type.listener')->onEntityTypeCreate($media_type);
}
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles(): void {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../../system/tests/fixtures/update/drupal-9.4.0.filled.standard.php.gz',
__DIR__ . '/../../../fixtures/update/media.php',
__DIR__ . '/../../../fixtures/update/media-oembed-iframe.php',
];
}
/**
* Test eager-load setting upgrade path.
*
* @see media_post_update_oembed_loading_attribute
*
* @legacy
*/
public function testUpdate(): void {
$this->expectDeprecation('The oEmbed loading attribute update for view display "media.remote_video.default" is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Profile, module and theme provided configuration should be updated. See https://www.drupal.org/node/3275103');
$data = EntityViewDisplay::load('media.remote_video.default')->toArray();
$this->assertArrayNotHasKey('loading', $data['content']['field_media_oembed_video']['settings']);
$this->runUpdates();
$data = EntityViewDisplay::load('media.remote_video.default')->toArray();
$this->assertArrayHasKey('loading', $data['content']['field_media_oembed_video']['settings']);
$this->assertEquals('eager', $data['content']['field_media_oembed_video']['settings']['loading']['attribute']);
}
}

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\FieldWidget;
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\media\Functional\MediaFunctionalTestBase;
/**
* @covers \Drupal\media\Plugin\Field\FieldWidget\OEmbedWidget
*
* @group media
*/
class OEmbedFieldWidgetTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that the oEmbed field widget shows the configured help text.
*/
public function testFieldWidgetHelpText(): void {
$account = $this->drupalCreateUser(['create media']);
$this->drupalLogin($account);
$media_type = $this->createMediaType('oembed:video');
$source_field = $media_type->getSource()
->getSourceFieldDefinition($media_type)
->getName();
/** @var \Drupal\field\Entity\FieldConfig $field */
$field = FieldConfig::loadByName('media', $media_type->id(), $source_field);
$field->setDescription('This is help text for oEmbed field.')
->save();
$this->drupalGet('media/add/' . $media_type->id());
$assert_session = $this->assertSession();
$assert_session->pageTextContains('This is help text for oEmbed field.');
$assert_session->pageTextContains('You can link to media from the following services: YouTube, Vimeo');
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
/**
* Generic module test for media.
*
* @group media
*/
class GenericTest extends GenericModuleTestBase {}

View File

@@ -0,0 +1,451 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
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
* @group #slow
*/
class MediaAccessTest extends MediaFunctionalTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// This is needed to provide the user cache context for a below assertion.
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Tests some access control functionality.
*/
public function testMediaAccess(): void {
$assert_session = $this->assertSession();
$media_type = $this->createMediaType('test');
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
// 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);
user_role_revoke_permissions($role->id(), ['view media']);
// Test 'create BUNDLE media' permission.
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$permissions = ['create ' . $media_type->id() . ' media'];
$this->grantPermissions($role, $permissions);
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
user_role_revoke_permissions($role->id(), $permissions);
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
// Verify the author can not view the unpublished media item without
// 'view own unpublished media' permission.
$this->grantPermissions($role, ['view media']);
$this->drupalGet('media/' . $user_media->id());
$this->assertNoCacheContext('user');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$previous_revision = $user_media->getLoadedRevisionId();
$user_media->setUnpublished()->setNewRevision();
$user_media->save();
$this->drupalGet('media/' . $user_media->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$access_result = $user_media->access('view', NULL, TRUE);
$this->assertSame("The user must be the owner and the 'view own unpublished media' permission is required when the media item is unpublished.", $access_result->getReason());
$this->grantPermissions($role, ['view own unpublished media']);
$this->drupalGet('media/' . $user_media->id());
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
// Test revision access - logged-in user.
$this->grantPermissions($role, ['view all media revisions']);
$this->drupalGet('media/' . $user_media->id() . '/revisions');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $user_media->getRevisionId() . '/view');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $previous_revision . '/view');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$role->revokePermission('view own unpublished media')->save();
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $user_media->getRevisionId() . '/view');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$user_media->setPublished()->setNewRevision();
$user_media->save();
// Revision access - logged-out user.
$this->drupalLogout();
$this->drupalGet('media/' . $user_media->id() . '/revisions');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $user_media->getRevisionId() . '/view');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $previous_revision . '/view');
$assert_session->statusCodeEquals(403);
// Reverse revision access testing changes.
$role
->revokePermission('view all media revisions')
->grantPermission('view own unpublished media')
->save();
$user_media->setPublished()->setNewRevision();
$user_media->save();
$this->drupalLogin($this->nonAdminUser);
// Test 'create media' permission.
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$permissions = ['create media'];
$this->grantPermissions($role, $permissions);
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
user_role_revoke_permissions($role->id(), $permissions);
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
// Test 'edit own BUNDLE media' and 'delete own BUNDLE media' permissions.
$this->drupalGet('media/' . $user_media->id() . '/edit');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $user_media->id() . '/delete');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$permissions = [
'edit own ' . $user_media->bundle() . ' media',
'delete own ' . $user_media->bundle() . ' media',
];
$this->grantPermissions($role, $permissions);
$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);
user_role_revoke_permissions($role->id(), $permissions);
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
// Test 'edit any BUNDLE media' and 'delete any BUNDLE media' permissions.
$this->drupalGet('media/' . $media->id() . '/edit');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $media->id() . '/delete');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$permissions = [
'edit any ' . $media->bundle() . ' media',
'delete any ' . $media->bundle() . ' media',
];
$this->grantPermissions($role, $permissions);
$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)->save();
$this->drupalGet('admin/content');
$assert_session->linkByHrefExists('/admin/content/media');
$this->clickLink('Media');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
$assert_session->elementExists('css', '.views-element-container');
// First row of the View contains media created by admin user.
$assert_session->elementTextEquals('xpath', '//div[@class="views-element-container"]//tbody/tr[1]/td[contains(@class, "views-field-uid")]/a', $this->adminUser->getDisplayName());
$assert_session->elementTextEquals('xpath', "//div[@class='views-element-container']//tbody/tr[1]/td[contains(@class, 'views-field-name')]/a[contains(@href, '/media/{$media->id()}')]", 'Unnamed');
// Second row of the View contains media created by non-admin user.
$assert_session->elementTextEquals('xpath', '//div[@class="views-element-container"]//tbody/tr[2]/td[contains(@class, "views-field-uid")]/a', $this->nonAdminUser->getDisplayName());
$assert_session->elementTextEquals('xpath', "//div[@class='views-element-container']//tbody/tr[2]/td[contains(@class, 'views-field-name')]/a[contains(@href, '/media/{$user_media->id()}')]", 'Unnamed');
}
/**
* Tests view access control on the canonical page.
*/
public function testCanonicalMediaAccess(): void {
$media_type = $this->createMediaType('test');
$assert_session = $this->assertSession();
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
// 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();
$this->drupalLogin($this->nonAdminUser);
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
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 when the media item is published.", $access_result->getReason());
$this->grantPermissions($role, ['view media']);
$this->drupalGet('media/' . $media->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
}
/**
* Tests unpublished media access.
*/
public function testUnpublishedMediaUserAccess(): void {
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
$assert_session = $this->assertSession();
$media_type = $this->createMediaType('test');
$permissions = [
'view media',
'view own unpublished media',
];
$user_one = $this->drupalCreateUser($permissions);
$user_two = $this->drupalCreateUser($permissions);
// Create media as user one.
$user_media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
'uid' => $user_one->id(),
]);
$user_media->setUnpublished()->save();
// Make sure user two can't access unpublished media.
$this->drupalLogin($user_two);
$this->drupalGet('media/' . $user_media->id());
$assert_session->statusCodeEquals(403);
$this->assertCacheContext('user');
$this->drupalLogout();
// Make sure user one can access own unpublished media.
$this->drupalLogin($user_one);
$this->drupalGet('media/' . $user_media->id());
$assert_session->statusCodeEquals(200);
$this->assertCacheContext('user');
}
/**
* Tests media access of anonymous user.
*/
public function testMediaAnonymousUserAccess(): void {
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
$assert_session = $this->assertSession();
$media_type = $this->createMediaType('test');
// Create media as anonymous user.
$user_media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
'uid' => 0,
]);
$user_media->save();
$role = Role::load(RoleInterface::ANONYMOUS_ID);
$this->grantPermissions($role, ['view media', 'view own unpublished media']);
$this->drupalLogout();
// Make sure anonymous users can access published media.
$user_media->setPublished()->save();
$this->drupalGet('media/' . $user_media->id());
$assert_session->statusCodeEquals(200);
// Make sure anonymous users can not access unpublished media
// even though role has 'view own unpublished media' permission.
$user_media->setUnpublished()->save();
$this->drupalGet('media/' . $user_media->id());
$assert_session->statusCodeEquals(403);
$this->assertCacheContext('user');
}
/**
* Tests access for embedded medias.
*/
public function testReferencedRendering(): void {
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
// Create a media type and an entity reference to itself.
$media_type = $this->createMediaType('test');
FieldStorageConfig::create([
'field_name' => 'field_reference',
'entity_type' => 'media',
'type' => 'entity_reference',
'settings' => [
'target_type' => 'media',
],
])->save();
FieldConfig::create([
'field_name' => 'field_reference',
'entity_type' => 'media',
'bundle' => $media_type->id(),
])->save();
$author = $this->drupalCreateUser([
'view media',
'view own unpublished media',
]);
$other_user = $this->drupalCreateUser([
'view media',
'view own unpublished media',
]);
$view_user = $this->drupalCreateUser(['view media']);
$child_title = 'Child media';
$media_child = Media::create([
'name' => $child_title,
'bundle' => $media_type->id(),
'uid' => $author->id(),
]);
$media_child->setUnpublished()->save();
$media_parent = Media::create([
'name' => 'Parent media',
'bundle' => $media_type->id(),
'field_reference' => $media_child->id(),
]);
$media_parent->save();
\Drupal::service('entity_display.repository')->getViewDisplay('media', $media_type->id(), 'full')
->set('content', [])
->setComponent('title', ['type' => 'string'])
->setComponent('field_reference', [
'type' => 'entity_reference_label',
])
->save();
$assert_session = $this->assertSession();
// The author of the child media items should have access to both the parent
// and child.
$this->drupalLogin($author);
$this->drupalGet($media_parent->toUrl());
$this->assertCacheContext('user');
$assert_session->pageTextContains($child_title);
// Other users with the 'view own unpublished media' permission should not
// be able to see the unpublished child media item. The 'user' cache context
// should be added in this case.
$this->drupalLogin($other_user);
$this->drupalGet($media_parent->toUrl());
$this->assertCacheContext('user');
$assert_session->pageTextNotContains($child_title);
// User with just the 'view media' permission should not be able to see the
// child media item. The 'user' cache context should not be added in this
// case.
$this->drupalLogin($view_user);
$this->drupalGet($media_parent->toUrl());
$this->assertNoCacheContext('user');
$assert_session->pageTextNotContains($child_title);
}
}

View File

@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\Media;
use Drupal\views\Views;
/**
* Tests a media bulk form.
*
* @group media
*/
class MediaBulkFormTest extends MediaFunctionalTestBase {
/**
* Modules to be enabled.
*
* @var array
*/
protected static $modules = ['media_test_views'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The test media type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testMediaType;
/**
* The test media items.
*
* @var \Drupal\media\MediaInterface[]
*/
protected $mediaItems;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->testMediaType = $this->createMediaType('test');
// Create some test media items.
$this->mediaItems = [];
for ($i = 1; $i <= 5; $i++) {
$media = Media::create([
'bundle' => $this->testMediaType->id(),
]);
$media->save();
$this->mediaItems[] = $media;
}
}
/**
* Tests the media bulk form.
*/
public function testBulkForm(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
// Check that all created items are present in the test view.
$view = Views::getView('test_media_bulk_form');
$view->execute();
$this->assertSame($view->total_rows, 5);
// Check the operations are accessible to the logged in user.
$this->drupalGet('test-media-bulk-form');
// Current available actions: Delete, Save, Publish, Unpublish.
$available_actions = [
'media_delete_action',
'media_publish_action',
'media_save_action',
'media_unpublish_action',
];
foreach ($available_actions as $action_name) {
$assert_session->optionExists('action', $action_name);
}
// Test unpublishing in bulk.
$page->checkField('media_bulk_form[0]');
$page->checkField('media_bulk_form[1]');
$page->checkField('media_bulk_form[2]');
$page->selectFieldOption('action', 'media_unpublish_action');
$page->pressButton('Apply to selected items');
$assert_session->pageTextContains('Unpublish media was applied to 3 items');
$this->assertFalse($this->storage->loadUnchanged(1)->isPublished(), 'The unpublish action failed in some of the media items.');
$this->assertFalse($this->storage->loadUnchanged(2)->isPublished(), 'The unpublish action failed in some of the media items.');
$this->assertFalse($this->storage->loadUnchanged(3)->isPublished(), 'The unpublish action failed in some of the media items.');
// Test publishing in bulk.
$page->checkField('media_bulk_form[0]');
$page->checkField('media_bulk_form[1]');
$page->selectFieldOption('action', 'media_publish_action');
$page->pressButton('Apply to selected items');
$assert_session->pageTextContains('Publish media was applied to 2 items');
$this->assertTrue($this->storage->loadUnchanged(1)->isPublished(), 'The publish action failed in some of the media items.');
$this->assertTrue($this->storage->loadUnchanged(2)->isPublished(), 'The publish action failed in some of the media items.');
// Test deletion in bulk.
$page->checkField('media_bulk_form[0]');
$page->checkField('media_bulk_form[1]');
$page->selectFieldOption('action', 'media_delete_action');
$page->pressButton('Apply to selected items');
$assert_session->pageTextContains('Are you sure you want to delete these media items?');
$page->pressButton('Delete');
$assert_session->pageTextContains('Deleted 2 items.');
$this->assertNull($this->storage->loadUnchanged(1), 'Could not delete some of the media items.');
$this->assertNull($this->storage->loadUnchanged(2), 'Could not delete some of the media items.');
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\media\Entity\Media;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\system\Functional\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the media items cache tags.
*
* @group media
*/
class MediaCacheTagsTest extends EntityWithUriCacheTagsTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'media',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a media type.
$mediaType = $this->createMediaType('test');
// 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(),
];
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\Media;
/**
* Tests views contextual links on media items.
*
* @group media
*/
class MediaContextualLinksTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'contextual',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests contextual links.
*/
public function testMediaContextualLinks(): void {
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
// Create a media type.
$mediaType = $this->createMediaType('test');
// Create a media item.
$media = Media::create([
'bundle' => $mediaType->id(),
'name' => 'Unnamed',
]);
$media->save();
$user = $this->drupalCreateUser([
'administer media',
'access contextual links',
]);
$this->drupalLogin($user);
$this->drupalGet('media/' . $media->id());
$this->assertSession()->elementAttributeContains('css', 'div[data-contextual-id]', 'data-contextual-id', 'media:media=' . $media->id() . ':');
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Base class for Media functional tests.
*/
abstract class MediaFunctionalTestBase extends BrowserTestBase {
use MediaFunctionalTestTrait;
use MediaTypeCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'system',
'node',
'field_ui',
'views_ui',
'media',
'media_test_source',
];
}

View File

@@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
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 module permissions.
'access media overview',
'administer media',
'administer media fields',
'administer media form display',
'administer media display',
'administer media types',
'view media',
// Other permissions.
'administer views',
'access content overview',
'view all revisions',
'administer content types',
'administer node fields',
'administer node form display',
'administer node 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(): void {
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');
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests media Install / Uninstall logic.
*
* @group media
*/
class MediaInstallTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer modules']));
}
/**
* Tests reinstalling after being uninstalled.
*/
public function testReinstallAfterUninstall(): void {
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
// Uninstall the media module.
$this->container->get('module_installer')->uninstall(['media'], FALSE);
$this->drupalGet('/admin/modules');
$page->checkField('modules[media][enable]');
$page->pressButton('Install');
$assert_session->pageTextNotContains('could not be moved/copied because a file by that name already exists in the destination directory');
$assert_session->pageTextContains('Module Media has been installed');
}
}

View File

@@ -0,0 +1,188 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\media\Entity\Media;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the Media overview page.
*
* @group media
*/
class MediaOverviewPageTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Make the site multilingual to have a working language field handler.
ConfigurableLanguage::create(['id' => 'es', 'title' => 'Spanish title', 'label' => 'Spanish label'])->save();
$this->drupalLogin($this->nonAdminUser);
}
/**
* Tests that the Media overview page (/admin/content/media).
*/
public function testMediaOverviewPage(): void {
$assert_session = $this->assertSession();
// Check the view exists, is access-restricted, and some defaults are there.
$this->drupalGet('/admin/content/media');
$assert_session->statusCodeEquals(403);
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
$this->grantPermissions($role, ['access media overview']);
$this->getSession()->reload();
$assert_session->statusCodeEquals(200);
$assert_session->titleEquals('Media | Drupal');
$assert_session->fieldExists('Media name');
$assert_session->selectExists('type');
$assert_session->selectExists('status');
$assert_session->selectExists('langcode');
$assert_session->buttonExists('Filter');
$header = $assert_session->elementExists('css', 'th#view-thumbnail-target-id-table-column');
$this->assertSame('Thumbnail', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-name-table-column');
$this->assertSame('Media name', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-bundle-table-column');
$this->assertSame('Type', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-uid-table-column');
$this->assertSame('Author', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-status-table-column');
$this->assertSame('Status', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-changed-table-column');
$this->assertSame('Updated Sort ascending', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-operations-table-column');
$this->assertSame('Operations', $header->getText());
$assert_session->pageTextContains('No media available.');
// Create some content for the view.
$media_type1 = $this->createMediaType('test');
$media_type2 = $this->createMediaType('test');
$media1 = Media::create([
'bundle' => $media_type1->id(),
'name' => 'Media 1',
'uid' => $this->adminUser->id(),
]);
$media1->save();
$media2 = Media::create([
'bundle' => $media_type2->id(),
'name' => 'Media 2',
'uid' => $this->adminUser->id(),
'status' => FALSE,
'changed' => time() - 50,
]);
$media2->save();
$media3 = Media::create([
'bundle' => $media_type1->id(),
'name' => 'Media 3',
'uid' => $this->nonAdminUser->id(),
'changed' => time() - 100,
]);
$media3->save();
// Make sure the role save below properly invalidates cache tags.
$this->refreshVariables();
// Verify the view is now correctly populated. The non-admin user can only
// view published media.
$this->grantPermissions($role, [
'view media',
'update any media',
'delete any media',
]);
$this->getSession()->reload();
$row1 = $assert_session->elementExists('css', 'table tbody tr:nth-child(1)');
$row2 = $assert_session->elementExists('css', 'table tbody tr:nth-child(2)');
// Media thumbnails.
$assert_session->elementExists('css', 'td.views-field-thumbnail__target-id img', $row1);
$assert_session->elementExists('css', 'td.views-field-thumbnail__target-id img', $row2);
// Media names.
$name1 = $assert_session->elementExists('css', 'td.views-field-name a', $row1);
$this->assertSame($media1->label(), $name1->getText());
$name2 = $assert_session->elementExists('css', 'td.views-field-name a', $row2);
$this->assertSame($media3->label(), $name2->getText());
$assert_session->linkByHrefExists('/media/' . $media1->id());
$assert_session->linkByHrefExists('/media/' . $media3->id());
// Media types.
$type_element1 = $assert_session->elementExists('css', 'td.views-field-bundle', $row1);
$this->assertSame($media_type1->label(), $type_element1->getText());
$type_element2 = $assert_session->elementExists('css', 'td.views-field-bundle', $row2);
$this->assertSame($media_type1->label(), $type_element2->getText());
// Media authors.
$author_element1 = $assert_session->elementExists('css', 'td.views-field-uid', $row1);
$this->assertSame($this->adminUser->getDisplayName(), $author_element1->getText());
$author_element3 = $assert_session->elementExists('css', 'td.views-field-uid', $row2);
$this->assertSame($this->nonAdminUser->getDisplayName(), $author_element3->getText());
// Media publishing status.
$status_element1 = $assert_session->elementExists('css', 'td.views-field-status', $row1);
$this->assertSame('Published', $status_element1->getText());
$status_element3 = $assert_session->elementExists('css', 'td.views-field-status', $row2);
$this->assertSame('Published', $status_element3->getText());
// Timestamp.
$expected = \Drupal::service('date.formatter')->format($media1->getChangedTime(), 'short');
$changed_element1 = $assert_session->elementExists('css', 'td.views-field-changed', $row1);
$this->assertSame($expected, $changed_element1->getText());
// Operations.
$assert_session->elementExists('css', 'td.views-field-operations li a:contains("Edit")', $row1);
$assert_session->linkByHrefExists('/media/' . $media1->id() . '/edit');
$assert_session->elementExists('css', 'td.views-field-operations li a:contains("Delete")', $row1);
$assert_session->linkByHrefExists('/media/' . $media1->id() . '/delete');
// Make sure the role save below properly invalidates cache tags.
$this->refreshVariables();
// Make the user the owner of the unpublished media item and assert the
// media item is only visible with the 'view own unpublished media'
// permission.
$media2->setOwner($this->nonAdminUser)->save();
$this->getSession()->reload();
$assert_session->pageTextNotContains($media2->label());
$role->grantPermission('view own unpublished media')->save();
$this->getSession()->reload();
$row = $assert_session->elementExists('css', 'table tbody tr:nth-child(2)');
$name = $assert_session->elementExists('css', 'td.views-field-name a', $row);
$this->assertSame($media2->label(), $name->getText());
$status_element = $assert_session->elementExists('css', 'td.views-field-status', $row);
$this->assertSame('Unpublished', $status_element->getText());
// Assert the admin user can always view all media.
$this->drupalLogin($this->adminUser);
$this->drupalGet('/admin/content/media');
$row1 = $assert_session->elementExists('css', 'table tbody tr:nth-child(1)');
$row2 = $assert_session->elementExists('css', 'table tbody tr:nth-child(2)');
$row3 = $assert_session->elementExists('css', 'table tbody tr:nth-child(3)');
$name1 = $assert_session->elementExists('css', 'td.views-field-name a', $row1);
$this->assertSame($media1->label(), $name1->getText());
$name2 = $assert_session->elementExists('css', 'td.views-field-name a', $row2);
$this->assertSame($media2->label(), $name2->getText());
$name3 = $assert_session->elementExists('css', 'td.views-field-name a', $row3);
$this->assertSame($media3->label(), $name3->getText());
$assert_session->linkByHrefExists('/media/' . $media1->id());
$assert_session->linkByHrefExists('/media/' . $media2->id());
$assert_session->linkByHrefExists('/media/' . $media3->id());
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
/**
* Tests the Media module's requirements checks.
*
* @group media
*/
class MediaRequirementsTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*
* @todo Remove and fix test to not rely on super user.
* @see https://www.drupal.org/project/drupal/issues/3437620
*/
protected bool $usesSuperUserAccessPolicy = TRUE;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that the requirements check can handle a missing source field.
*/
public function testMissingSourceFieldDefinition(): void {
$media_type = $this->createMediaType('test');
/** @var \Drupal\field\FieldConfigInterface $field_definition */
$field_definition = $media_type->getSource()
->getSourceFieldDefinition($media_type);
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage_definition */
$field_storage_definition = $field_definition->getFieldStorageDefinition();
$field_definition->delete();
$field_storage_definition->delete();
$valid_media_type = $this->createMediaType('test');
$this->drupalLogin($this->rootUser);
$this->drupalGet('/admin/reports/status');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains("The source field definition for the {$media_type->label()} media type is missing.");
$this->assertSession()->pageTextNotContains("The source field definition for the {$valid_media_type->label()} media type is missing.");
}
}

View File

@@ -0,0 +1,351 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\media\Entity\Media;
use Drupal\media\MediaInterface;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the revisions of media entities.
*
* @group media
* @group #slow
*/
class MediaRevisionTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->createMediaType('test', ['id' => 'test', 'label' => 'test']);
}
/**
* Creates a media item.
*
* @param string $title
* Title of media item.
*
* @return \Drupal\media\Entity\Media
* A media item.
*/
protected function createMedia(string $title): Media {
$media = Media::create([
'bundle' => 'test',
'name' => $title,
]);
$media->save();
return $media;
}
/**
* Checks media revision operations.
*/
public function testRevisions(): void {
$assert = $this->assertSession();
$media = $this->createMedia('Sample media');
$originalRevisionId = $media->getRevisionId();
// You can access the revision page when there is only 1 revision.
$this->drupalGet($media->toUrl('revision'));
$assert->statusCodeEquals(200);
// Create some revisions.
$revision_count = 3;
for ($i = 0; $i < $revision_count; $i++) {
$media->revision_log = $this->randomMachineName(32);
$media = $this->createMediaRevision($media);
}
// Confirm that the last revision is the default revision.
$this->assertTrue($media->isDefaultRevision(), 'Last revision is the default.');
// Get the original revision for simple checks.
$media = \Drupal::entityTypeManager()->getStorage('media')
->loadRevision($originalRevisionId);
// 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 all media revisions',
]);
$this->drupalGet($media->toUrl('revision'));
$assert->statusCodeEquals(403);
$this->grantPermissions($role, ['view any test media revisions']);
$this->drupalGet($media->toUrl('revision'));
$assert->statusCodeEquals(200);
user_role_revoke_permissions($role->id(), ['view any test media revisions']);
$this->grantPermissions($role, ['view all media revisions']);
$this->drupalGet($media->toUrl('revision'));
$assert->statusCodeEquals(200);
// Confirm the revision page shows the correct title.
$assert->pageTextContains($media->getName());
}
/**
* Tests creating revisions of a File media item.
*/
public function testFileMediaRevision(): void {
$assert = $this->assertSession();
$uri = 'temporary://foo.txt';
file_put_contents($uri, $this->randomString(128));
$this->createMediaType('file', ['id' => 'document', 'new_revision' => TRUE]);
// Create a media item.
$this->drupalGet('/media/add/document');
$page = $this->getSession()->getPage();
$page->fillField('Name', 'Foobar');
$page->attachFileToField('File', $this->container->get('file_system')->realpath($uri));
$page->pressButton('Save');
$assert->addressEquals('admin/content/media');
// 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', 'Foo');
$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('Foo');
}
/**
* Tests creating revisions of an Image media item.
*/
public function testImageMediaRevision(): void {
$assert = $this->assertSession();
$this->createMediaType('image', ['id' => 'image', 'new_revision' => TRUE]);
/** @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', $this->root . '/core/modules/media/tests/fixtures/example_1.jpeg');
$page->pressButton('Save');
$assert->addressEquals('admin/content/media');
// 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', 'Foo');
$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('Foo');
}
/**
* 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.
*
* @internal
*/
protected function assertRevisionCount(EntityInterface $entity, int $expected_revisions): void {
$entity_type = $entity->getEntityType();
$count = $this->container
->get('entity_type.manager')
->getStorage($entity_type->id())
->getQuery()
->accessCheck(FALSE)
->count()
->allRevisions()
->condition($entity_type->getKey('id'), $entity->id())
->execute();
$this->assertSame($expected_revisions, (int) $count);
}
/**
* Creates a media with a revision.
*
* @param \Drupal\media\Entity\Media $media
* The media object.
*/
private function createMediaWithRevision(Media $media): void {
$media->setNewRevision();
$media->setName('1st changed title');
$media->setRevisionLogMessage('first revision');
// Set revision creation time to check the confirmation message while
// deleting or reverting a revision.
$media->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp());
$media->save();
}
/**
* Tests deleting a revision.
*/
public function testRevisionDelete(): void {
$user = $this->drupalCreateUser([
'edit any test media',
'view any test media revisions',
'delete any test media revisions',
]);
$this->drupalLogin($user);
$media = $this->createMedia('Sample media');
$this->createMediaWithRevision($media);
$originalRevisionId = $media->getRevisionId();
// Cannot delete latest revision.
$this->drupalGet($media->toUrl('revision-delete-form'));
$this->assertSession()->statusCodeEquals(403);
// Create a new revision.
$media->setNewRevision();
$media->setRevisionLogMessage('second revision')
->setRevisionCreationTime((new \DateTimeImmutable('12 March 2012 5pm'))->getTimestamp())
->setName('Sample media updated')
->save();
$this->drupalGet($media->toUrl('version-history'));
$this->assertSession()->pageTextContains("First revision");
$this->assertSession()->pageTextContains("Second revision");
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
// Reload the previous revision, and ensure we can delete it in the UI.
$revision = \Drupal::entityTypeManager()->getStorage('media')
->loadRevision($originalRevisionId);
$this->drupalGet($revision->toUrl('revision-delete-form'));
$this->assertSession()->pageTextContains('Are you sure you want to delete the revision from Sun, 01/11/2009 - 16:00?');
$this->submitForm([], 'Delete');
$this->assertSession()->pageTextNotContains("First revision");
$this->assertSession()->pageTextContains("Second revision");
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals(sprintf('media/%s/revisions', $media->id()));
$this->assertSession()->pageTextContains('Revision from Sun, 01/11/2009 - 16:00 of test 1st changed title has been deleted.');
// Check that only two revisions exists, i.e. the original and the latest
// revision.
$this->assertSession()->elementsCount('css', 'table tbody tr', 2);
}
/**
* Tests reverting a revision.
*/
public function testRevisionRevert(): void {
/** @var \Drupal\user\UserInterface $user */
$user = $this->drupalCreateUser([
'edit any test media',
'view any test media revisions',
'revert any test media revisions',
]);
$this->drupalLogin($user);
$media = $this->createMedia('Initial title');
$this->createMediaWithRevision($media);
$originalRevisionId = $media->getRevisionId();
$originalRevisionLabel = $media->getName();
// Cannot revert latest revision.
$this->drupalGet($media->toUrl('revision-revert-form'));
$this->assertSession()->statusCodeEquals(403);
// Create a new revision.
$media->setNewRevision();
$media->setRevisionLogMessage('Second revision')
->setRevisionCreationTime((new \DateTimeImmutable('12 March 2012 5pm'))->getTimestamp())
->setName('Sample media updated')
->save();
$this->drupalGet($media->toUrl('version-history'));
$this->assertSession()->pageTextContains("First revision");
$this->assertSession()->pageTextContains("Second revision");
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
// Reload the previous revision, and ensure we can revert to it in the UI.
$revision = \Drupal::entityTypeManager()->getStorage('media')
->loadRevision($originalRevisionId);
$this->drupalGet($revision->toUrl('revision-revert-form'));
$this->assertSession()->pageTextContains('Are you sure you want to revert to the revision from Sun, 01/11/2009 - 16:00?');
$this->submitForm([], 'Revert');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('Copy of the revision from Sun, 01/11/2009 - 16:00');
$this->assertSession()->addressEquals(sprintf('media/%s/revisions', $media->id()));
$this->assertSession()->pageTextContains(sprintf('test %s has been reverted to the revision from Sun, 01/11/2009 - 16:00.', $originalRevisionLabel));
$this->assertSession()->elementsCount('css', 'table tbody tr', 4);
$this->drupalGet($media->toUrl('edit-form'));
// Check if the title is changed to the reverted revision.
$this->assertSession()->pageTextContains('1st changed title');
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Core\Url;
/**
* Testing the media settings.
*
* @group media
*/
class MediaSettingsTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->createUser([
'administer site configuration',
'administer media',
]));
}
/**
* Tests that media warning appears if oEmbed media types exists.
*/
public function testStatusPage(): void {
$assert_session = $this->assertSession();
$this->drupalGet('admin/reports/status');
$assert_session->pageTextNotContains('It is potentially insecure to display oEmbed content in a frame');
$this->createMediaType('oembed:video');
$this->drupalGet('admin/reports/status');
$assert_session->pageTextContains('It is potentially insecure to display oEmbed content in a frame');
}
/**
* Tests that the media settings form stores a `null` iFrame domain.
*/
public function testSettingsForm(): void {
$assert_session = $this->assertSession();
$this->assertNull($this->config('media.settings')->get('iframe_domain'));
$this->drupalGet(Url::fromRoute('media.settings'));
$assert_session->fieldExists('iframe_domain');
// Explicitly submitting an empty string does not result in the
// `iframe_domain` property getting set to the empty string: it is converted
// to `null` to comply with the config schema.
// @see \Drupal\media\Form\MediaSettingsForm::submitForm()
$this->submitForm([
'iframe_domain' => '',
], 'Save configuration');
$assert_session->statusMessageContains('The configuration options have been saved.', 'status');
$this->assertNull($this->config('media.settings')->get('iframe_domain'));
}
}

View File

@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\field\Entity\FieldConfig;
/**
* Tests the file media source.
*
* @group media
*/
class MediaSourceFileTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that it's possible to change the allowed file extensions.
*/
public function testSourceFieldSettingsEditing(): void {
$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(): void {
$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);
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the image media source.
*
* @group media
*/
class MediaSourceImageTest extends MediaFunctionalTestBase {
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Test that non-main properties do not trigger source field value change.
*/
public function testOnlyMainPropertiesTriggerSourceFieldChanged(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$media_type = $this->createMediaType('image');
$media_type_id = $media_type->id();
$media_type->setFieldMap(['name' => 'name']);
$media_type->save();
/** @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.$media_type_id.field_media_image");
$settings = $field->getSettings();
$settings['alt_field'] = TRUE;
$settings['alt_field_required'] = FALSE;
$field->set('settings', $settings);
$field->save();
$file = File::create([
'uri' => $this->getTestFiles('image')[0]->uri,
]);
$file->save();
$media = Media::create([
'name' => 'Custom name',
'bundle' => $media_type_id,
'field_media_image' => $file->id(),
]);
$media->save();
// Change only the alt of the image.
$this->drupalGet($media->toUrl('edit-form'));
$this->submitForm(['field_media_image[0][alt]' => 'Alt text'], 'Save');
// Custom name should stay.
$this->drupalGet($media->toUrl('edit-form'));
$assert_session->fieldValueEquals('name[0][value]', 'Custom name');
// Remove image and attach a new one.
$this->submitForm([], 'Remove');
$image_media_name = 'example_1.jpeg';
$page->attachFileToField('files[field_media_image_0]', $this->root . '/core/modules/media/tests/fixtures/' . $image_media_name);
$page->pressButton('Save');
$this->drupalGet($media->toUrl('edit-form'));
$assert_session->fieldValueEquals('name[0][value]', 'example_1.jpeg');
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
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
*/
protected static $modules = ['media'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests template suggestions from media_theme_suggestions_media().
*/
public function testMediaThemeHookSuggestions(): void {
$media_type = $this->createMediaType('test', [
'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->assertSame($suggestions, ['media__full', 'media__' . $media_type->id(), 'media__' . $media_type->id() . '__full', 'media__source_' . $media_type->getSource()->getPluginId()], 'Found expected media suggestions.');
}
}

View File

@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Tests the Media Translation UI.
*
* @group media
*/
class MediaTranslationUITest extends ContentTranslationUITestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $defaultCacheContexts = [
'languages:language_interface',
'session',
'theme',
'url.path',
'url.query_args',
'user.permissions',
'user.roles:authenticated',
];
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
'content_translation',
'media',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->entityTypeId = 'media';
$this->bundle = 'test';
parent::setUp();
$this->doSetup();
}
/**
* {@inheritdoc}
*/
public function setupBundle() {
$this->createMediaType('test', [
'id' => $this->bundle,
'queue_thumbnail_downloads' => FALSE,
]);
}
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), [
'administer media',
'edit any test media',
]);
}
/**
* {@inheritdoc}
*/
protected function getEditorPermissions() {
return ['administer media', 'create test media'];
}
/**
* {@inheritdoc}
*/
protected function getAdministratorPermissions() {
return array_merge(parent::getAdministratorPermissions(), [
'access administration pages',
'administer media types',
'access media overview',
'administer languages',
]);
}
/**
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return [
'name' => [['value' => $this->randomMachineName()]],
'field_media_test' => [['value' => $this->randomMachineName()]],
] + parent::getNewEntityValues($langcode);
}
}

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\MediaType;
use Drupal\TestTools\Random;
/**
* Ensures that media UI works correctly without JavaScript.
*
* @group media
*/
class MediaTypeCreationTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the media type creation form with only the mandatory options.
*
* @dataProvider providerMediaTypeCreationForm
*/
public function testMediaTypeCreationForm($button_label, $address, $machine_name): void {
$this->drupalGet('/admin/structure/media/add');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->fieldExists('label')->setValue($this->randomString());
$this->assertSession()->fieldExists('id')->setValue($machine_name);
$this->assertSession()->selectExists('source')->selectOption('test');
$this->assertSession()->buttonExists($button_label)->press();
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->fieldValueEquals('Test config value', 'This is default value.');
$this->assertSession()->buttonExists($button_label)->press();
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals($address);
$this->assertInstanceOf(MediaType::class, MediaType::load($machine_name));
}
/**
* Data provider for testMediaTypeCreationForm().
*/
public static function providerMediaTypeCreationForm() {
$machine_name = Random::machineName();
return [
[
'Save',
'admin/structure/media',
$machine_name,
],
[
'Save and manage fields',
'admin/structure/media/manage/' . $machine_name . '/fields',
$machine_name,
],
];
}
}

View File

@@ -0,0 +1,276 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
/**
* Ensures that media UI works correctly.
*
* @group media
* @group #slow
*/
class MediaUiFunctionalTest extends MediaFunctionalTestBase {
use FieldUiTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'block',
'media_test_source',
'media',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Tests the media actions (add/edit/delete).
*/
public function testMediaWithOnlyOneMediaType(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$media_type = $this->createMediaType('test', [
'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);
$source_field = $this->randomString();
$page->fillField('field_media_test[0][value]', $source_field);
$page->pressButton('Save');
$media_id = $this->container->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->execute();
$media_id = reset($media_id);
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertSame($media->getRevisionLogMessage(), $revision_log_message);
$this->assertSame($media->getName(), $media_name);
// 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->assertSame($media->getName(), $media_name2);
// Change the authored by field to an empty string, which should assign
// authorship to the anonymous user (uid 0).
$this->drupalGet('media/' . $media_id . '/edit');
$edit['uid[0][target_id]'] = '';
$this->submitForm($edit, 'Save');
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$uid = $media->getOwnerId();
// Most SQL database drivers stringify fetches but entities are not
// necessarily stored in a SQL database. At the same time, NULL/FALSE/""
// won't do.
$this->assertTrue($uid === 0 || $uid === '0', 'Media authored by anonymous user.');
// 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');
$this->drupalGet('media/' . $media_id);
$assert_session->statusCodeEquals(404);
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertSame($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')->accessCheck(FALSE)->execute();
$this->assertEmpty($media_id);
}
/**
* Tests the "media/add" page.
*
* Tests if the "media/add" page gives you a selecting option if there are
* multiple media types available.
*/
public function testMediaWithMultipleMediaTypes(): void {
$assert_session = $this->assertSession();
// Tests and creates the first media type.
$first_media_type = $this->createMediaType('test', ['description' => $this->randomMachineName()]);
// Test and create a second media type.
$second_media_type = $this->createMediaType('test', ['description' => $this->randomMachineName()]);
// 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());
}
/**
* Tests that media in ER fields use the Rendered Entity formatter by default.
*/
public function testRenderedEntityReferencedMedia(): void {
$assert_session = $this->assertSession();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
$this->createMediaType('image', ['id' => 'image', 'new_revision' => TRUE]);
$this->fieldUIAddNewField('/admin/structure/types/manage/page', 'foo_field', 'Foo field', 'field_ui:entity_reference:media', [], ['settings[handler_settings][target_bundles][image]' => TRUE]);
$this->drupalGet('/admin/structure/types/manage/page/display');
$assert_session->fieldValueEquals('fields[field_foo_field][type]', 'entity_reference_entity_view');
}
/**
* Tests the redirect URL after creating a media item.
*/
public function testMediaCreateRedirect(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->createMediaType('test', [
'queue_thumbnail_downloads' => FALSE,
]);
// Test a redirect to the media canonical URL for a user without the 'access
// media overview' permission.
$this->drupalLogin($this->drupalCreateUser([
'view media',
'create media',
]));
$this->drupalGet('media/add');
$page->fillField('name[0][value]', $this->randomMachineName());
$page->fillField('field_media_test[0][value]', $this->randomString());
$page->pressButton('Save');
$media_id = $this->container->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->execute();
$media_id = reset($media_id);
$assert_session->addressEquals("media/$media_id/edit");
// Test a redirect to the media overview for a user with the 'access media
// overview' permission.
$this->drupalLogin($this->drupalCreateUser([
'view media',
'create media',
'access media overview',
]));
$this->drupalGet('media/add');
$page->fillField('name[0][value]', $this->randomMachineName());
$page->fillField('field_media_test[0][value]', $this->randomString());
$page->pressButton('Save');
$assert_session->addressEquals('admin/content/media');
}
/**
* Tests the media collection route.
*/
public function testMediaCollectionRoute(): void {
/** @var \Drupal\Core\Entity\EntityStorageInterface $media_storage */
$media_storage = $this->container->get('entity_type.manager')->getStorage('media');
$this->container->get('module_installer')->uninstall(['views']);
// Create a media type and media item.
$media_type = $this->createMediaType('test');
$media = $media_storage->create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
]);
$media->save();
$this->drupalGet($media->toUrl('collection'));
$assert_session = $this->assertSession();
// Media list table exists.
$assert_session->elementExists('css', 'th:contains("Media Name")');
$assert_session->elementExists('css', 'th:contains("Type")');
$assert_session->elementExists('css', 'th:contains("Operations")');
// Media item is present.
$assert_session->elementExists('css', 'td:contains("Unnamed")');
}
}

View File

@@ -0,0 +1,358 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Behat\Mink\Element\NodeElement;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
/**
* Ensures that media UI works correctly.
*
* @group media
* @group #slow
*/
class MediaUiReferenceWidgetTest extends MediaFunctionalTestBase {
use FieldUiTestTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'block',
'media_test_source',
'media',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Data provider for testMediaReferenceWidget().
*
* @return array[]
* Test data. See testMediaReferenceWidget() for the child array structure.
*/
public static function providerTestMediaReferenceWidget() {
return [
// Single-value fields with a single media type and the default widget:
// - The user can create and list the media.
'single_value:single_type:create_list' => [1, [TRUE], TRUE],
// - The user can list but not create the media.
'single_value:single_type:list' => [1, [FALSE], TRUE],
// - The user can create but not list the media.
'single_value:single_type:create' => [1, [TRUE], FALSE],
// - The user can neither create nor list the media.
'single_value:single_type' => [1, [FALSE], FALSE],
// Single-value fields with the tags-style widget:
// - The user can create and list the media.
'single_value:single_type:create_list:tags' => [1, [TRUE], TRUE, 'entity_reference_autocomplete_tags'],
// - The user can list but not create the media.
'single_value:single_type:list:tags' => [1, [FALSE], TRUE, 'entity_reference_autocomplete_tags'],
// - The user can create but not list the media.
'single_value:single_type:create:tags' => [1, [TRUE], FALSE, 'entity_reference_autocomplete_tags'],
// - The user can neither create nor list the media.
'single_value:single_type:tags' => [1, [FALSE], FALSE, 'entity_reference_autocomplete_tags'],
// Single-value fields with two media types:
// - The user can create both types.
'single_value:two_type:create2_list' => [1, [TRUE, TRUE], TRUE],
// - The user can create only one type.
'single_value:two_type:create1_list' => [1, [TRUE, FALSE], TRUE],
// - The user cannot create either type.
'single_value:two_type:list' => [1, [FALSE, FALSE], TRUE],
// Multiple-value field with a cardinality of 3, with media the user can
// create and list.
'multi_value:single_type:create_list' => [3, [TRUE], TRUE],
// The same, with the tags field.
'multi-value:single_type:create_list:tags' => [3, [TRUE], TRUE, 'entity_reference_autocomplete_tags'],
// Unlimited value field.
'unlimited_value:single_type:create_list' => [FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, [TRUE], TRUE],
// Unlimited value field with the tags widget.
'unlimited_value:single_type:create_list:tags' => [FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, [TRUE], TRUE, 'entity_reference_autocomplete_tags'],
];
}
/**
* Tests the default autocomplete widgets for media reference fields.
*
* @param int $cardinality
* The field cardinality.
* @param bool[] $media_type_create_access
* An array of booleans indicating whether to grant the test user create
* access for each media type. A media type is created automatically for
* each; for example, an array [TRUE, FALSE] would create two media types,
* one that allows the user to create media and a second that does not.
* @param bool $list_access
* Whether to grant the test user access to list media.
* @param string $widget_id
* The widget ID to test.
*
* @see media_field_widget_entity_reference_autocomplete_form_alter()
* @see media_field_widget_multiple_entity_reference_autocomplete_form_alter()
*
* @dataProvider providerTestMediaReferenceWidget
*/
public function testMediaReferenceWidget($cardinality, array $media_type_create_access, $list_access, $widget_id = 'entity_reference_autocomplete'): void {
$assert_session = $this->assertSession();
// Create two content types.
$non_media_content_type = $this->createContentType();
$content_type = $this->createContentType();
// Create some media types.
$media_types = [];
$permissions = [];
$create_media_types = [];
foreach ($media_type_create_access as $id => $access) {
if ($access) {
$create_media_types[] = "media_type_$id";
$permissions[] = "create media_type_$id media";
}
$this->createMediaType('test', [
'id' => "media_type_$id",
'label' => "media_type_$id",
]);
$media_types["media_type_$id"] = "media_type_$id";
}
// Create a user that can create content of the type, with other
// permissions as given by the data provider.
$permissions[] = "create {$content_type->id()} content";
if ($list_access) {
$permissions[] = "access media overview";
}
$test_user = $this->drupalCreateUser($permissions);
// Create a non-media entity reference.
$non_media_storage = FieldStorageConfig::create([
'field_name' => 'field_not_a_media_field',
'entity_type' => 'node',
'type' => 'entity_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'target_type' => 'node',
],
]);
$non_media_storage->save();
$non_media_field = FieldConfig::create([
'label' => 'No media here!',
'field_storage' => $non_media_storage,
'entity_type' => 'node',
'bundle' => $non_media_content_type->id(),
'settings' => [
'handler' => 'default',
'handler_settings' => [
'target_bundles' => [
$non_media_content_type->id() => $non_media_content_type->id(),
],
],
],
]);
$non_media_field->save();
\Drupal::entityTypeManager()
->getStorage('entity_form_display')
->load('node.' . $non_media_content_type->id() . '.default')
->setComponent('field_not_a_media_field', [
'type' => $widget_id,
])
->save();
// Create a media field through the user interface to ensure that the
// help text handling does not break the default value entry on the field
// settings form.
// Using submitForm() to avoid dealing with JavaScript on the previous
// page in the field creation.
$field_edit = [];
foreach ($media_types as $type) {
$field_edit["settings[handler_settings][target_bundles][$type]"] = TRUE;
}
$this->fieldUIAddNewField("admin/structure/types/manage/{$content_type->id()}", 'media_reference', "Media (cardinality $cardinality)", 'field_ui:entity_reference:media', [], $field_edit);
\Drupal::entityTypeManager()
->getStorage('entity_form_display')
->load('node.' . $content_type->id() . '.default')
->setComponent('field_media_reference', [
'type' => $widget_id,
])
->save();
// Some of the expected texts.
$create_help = 'Create your media on the media add page (opens a new window), then add it by name to the field below.';
$list_text = 'See the media list (opens a new window) to help locate media.';
$use_help = 'Type part of the media name.';
$create_header = "Create new media";
$use_header = "Use existing media";
// First check that none of the help texts are on the non-media content.
$this->drupalGet("/node/add/{$non_media_content_type->id()}");
$this->assertNoHelpTexts([
$create_header,
$create_help,
$use_header,
$use_help,
$list_text,
'Allowed media types:',
]);
// Now, check that the widget displays the expected help text under the
// given conditions for the test user.
$this->drupalLogin($test_user);
$this->drupalGet("/node/add/{$content_type->id()}");
// Specific expected help texts for the media field.
$create_header = "Create new media";
$use_header = "Use existing media";
$type_list = 'Allowed media types: ' . implode(", ", array_keys($media_types));
$fieldset_selector = '#edit-field-media-reference-wrapper fieldset';
$fieldset = $assert_session->elementExists('css', $fieldset_selector);
$this->assertSame("Media (cardinality $cardinality)", $assert_session->elementExists('css', 'legend', $fieldset)->getText());
// Assert text that should be displayed regardless of other access.
$this->assertHelpTexts([$use_header, $use_help, $type_list], $fieldset_selector);
// The entire section for creating new media should only be displayed if
// the user can create at least one media of the type.
if ($create_media_types) {
if (count($create_media_types) === 1) {
$url = Url::fromRoute('entity.media.add_form')->setRouteParameter('media_type', $create_media_types[0]);
}
else {
$url = Url::fromRoute('entity.media.add_page');
}
$this->assertHelpTexts([$create_header, $create_help], $fieldset_selector);
$this->assertHelpLink(
$fieldset,
'media add page',
[
'target' => '_blank',
'href' => $url->toString(),
]
);
}
else {
$this->assertNoHelpTexts([$create_header, $create_help]);
$this->assertNoHelpLink($fieldset, 'media add page');
}
if ($list_access) {
$this->assertHelpTexts([$list_text], $fieldset_selector);
$this->assertHelpLink(
$fieldset,
'media list',
[
'target' => '_blank',
'href' => Url::fromRoute('entity.media.collection')->toString(),
]
);
}
else {
$this->assertNoHelpTexts([$list_text]);
$this->assertNoHelpLink($fieldset, 'media list');
}
}
/**
* Asserts that the given texts are present exactly once.
*
* @param string[] $texts
* A list of the help texts to check.
* @param string $selector
* (optional) The selector to search.
*
* @internal
*/
protected function assertHelpTexts(array $texts, string $selector = ''): void {
$assert_session = $this->assertSession();
foreach ($texts as $text) {
// We only want to escape single quotes, so use str_replace() rather than
// addslashes().
$text = str_replace("'", "\'", $text);
if ($selector) {
$assert_session->elementsCount('css', $selector . ":contains('$text')", 1);
}
else {
$assert_session->pageTextContains($text);
}
}
}
/**
* Asserts that none of the given texts are present.
*
* @param string[] $texts
* A list of the help texts to check.
*
* @internal
*/
protected function assertNoHelpTexts(array $texts): void {
$assert_session = $this->assertSession();
foreach ($texts as $text) {
$assert_session->pageTextNotContains($text);
}
}
/**
* Asserts whether a given link is present.
*
* @param \Behat\Mink\Element\NodeElement $element
* The element to search.
* @param string $text
* The link text.
* @param string[] $attributes
* An associative array of any expected attributes, keyed by the
* attribute name.
*
* @internal
*/
protected function assertHelpLink(NodeElement $element, string $text, array $attributes = []): void {
// Find all the links inside the element.
$link = $element->findLink($text);
$this->assertNotEmpty($link);
foreach ($attributes as $attribute => $value) {
$this->assertSame($link->getAttribute($attribute), $value);
}
}
/**
* Asserts that a given link is not present.
*
* @param \Behat\Mink\Element\NodeElement $element
* The element to search.
* @param string $text
* The link text.
*
* @internal
*/
protected function assertNoHelpLink(NodeElement $element, string $text): void {
$assert_session = $this->assertSession();
// Assert that the link and its text are not present anywhere on the page.
$assert_session->elementNotExists('named', ['link', $text], $element);
$assert_session->pageTextNotContains($text);
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\media\OEmbed\Resource;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
// cspell:ignore dailymotion Schipulcon
/**
* Tests the oEmbed resource fetcher service.
*
* @coversDefaultClass \Drupal\media\OEmbed\ResourceFetcher
*
* @group media
*/
class ResourceFetcherTest extends MediaFunctionalTestBase {
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->useFixtureProviders();
$this->lockHttpClientToFixtures();
}
/**
* Data provider for testFetchResource().
*
* @return array
*/
public static function providerFetchResource() {
return [
'JSON resource' => [
'video_vimeo.json',
'Vimeo',
'Drupal Rap Video - Schipulcon09',
],
'XML resource' => [
'video_dailymotion.xml',
'Dailymotion',
"#d8rules - Support the Rules module for Drupal 8",
],
];
}
/**
* Tests resource fetching.
*
* @param string $resource_url
* The URL of the resource to fetch, relative to the base URL.
* @param string $provider_name
* The expected name of the resource provider.
* @param string $title
* The expected title of the resource.
*
* @covers ::fetchResource
*
* @dataProvider providerFetchResource
*/
public function testFetchResource($resource_url, $provider_name, $title): void {
/** @var \Drupal\media\OEmbed\Resource $resource */
$resource = $this->container->get('media.oembed.resource_fetcher')
->fetchResource($resource_url);
$this->assertInstanceOf(Resource::class, $resource);
$this->assertSame($provider_name, $resource->getProvider()->getName());
$this->assertSame($title, $resource->getTitle());
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class MediaJsonAnonTest extends MediaResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class MediaJsonBasicAuthTest extends MediaResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class MediaJsonCookieTest extends MediaResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,474 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\rest\RestResourceConfigInterface;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\user\RoleInterface;
use GuzzleHttp\RequestOptions;
abstract class MediaResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'media';
/**
* @var \Drupal\media\MediaInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [
'changed' => NULL,
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
// Provisioning the Media REST resource without the File REST resource does
// not make sense.
$this->resourceConfigStorage->create([
'id' => 'entity.file',
'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
'configuration' => [
'methods' => ['GET'],
'formats' => [static::$format],
'authentication' => isset(static::$auth) ? [static::$auth] : [],
],
'status' => TRUE,
])->save();
$this->container->get('router.builder')->rebuild();
}
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this->grantPermissionsToTestedRole(['view media']);
break;
case 'POST':
$this->grantPermissionsToTestedRole(['create camelids media', 'access content']);
break;
case 'PATCH':
$this->grantPermissionsToTestedRole(['edit any camelids media']);
// @todo Remove this in https://www.drupal.org/node/2824851.
$this->grantPermissionsToTestedRole(['access content']);
break;
case 'DELETE':
$this->grantPermissionsToTestedRole(['delete any camelids media']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
if (!MediaType::load('camelids')) {
// Create a "Camelids" media type.
$media_type = MediaType::create([
'label' => 'Camelids',
'id' => 'camelids',
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
'source' => 'file',
]);
$media_type->save();
// Create the source field.
$source_field = $media_type->getSource()->createSourceField($media_type);
$source_field->getFieldStorageDefinition()->save();
$source_field->save();
$media_type
->set('source_configuration', [
'source_field' => $source_field->getName(),
])
->save();
}
// Create a file to upload.
$file = File::create([
'uri' => 'public://llama.txt',
]);
$file->setPermanent();
$file->save();
// Create a "Llama" media item.
$media = Media::create([
'bundle' => 'camelids',
'field_media_file' => [
'target_id' => $file->id(),
],
]);
$media
->setName('Llama')
->setPublished()
->setCreatedTime(123456789)
->setOwnerId(static::$auth ? $this->account->id() : 0)
->setRevisionUserId(static::$auth ? $this->account->id() : 0)
->save();
return $media;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
$file = File::load(1);
$thumbnail = File::load(2);
$author = User::load($this->entity->getOwnerId());
return [
'mid' => [
[
'value' => 1,
],
],
'uuid' => [
[
'value' => $this->entity->uuid(),
],
],
'vid' => [
[
'value' => 1,
],
],
'langcode' => [
[
'value' => 'en',
],
],
'bundle' => [
[
'target_id' => 'camelids',
'target_type' => 'media_type',
'target_uuid' => MediaType::load('camelids')->uuid(),
],
],
'name' => [
[
'value' => 'Llama',
],
],
'field_media_file' => [
[
'description' => NULL,
'display' => NULL,
'target_id' => (int) $file->id(),
'target_type' => 'file',
'target_uuid' => $file->uuid(),
'url' => $file->createFileUrl(FALSE),
],
],
'thumbnail' => [
[
'alt' => '',
'width' => 180,
'height' => 180,
'target_id' => (int) $thumbnail->id(),
'target_type' => 'file',
'target_uuid' => $thumbnail->uuid(),
'title' => NULL,
'url' => $thumbnail->createFileUrl(FALSE),
],
],
'status' => [
[
'value' => TRUE,
],
],
'created' => [
[
'value' => (new \DateTime())->setTimestamp(123456789)->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'changed' => [
[
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'revision_created' => [
[
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getRevisionCreationTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'default_langcode' => [
[
'value' => TRUE,
],
],
'uid' => [
[
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => base_path() . 'user/' . $author->id(),
],
],
'revision_user' => [
[
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => base_path() . 'user/' . $author->id(),
],
],
'revision_translation_affected' => [
[
'value' => TRUE,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'bundle' => [
[
'target_id' => 'camelids',
],
],
'name' => [
[
'value' => 'Drama llama',
],
],
'field_media_file' => [
[
'description' => NULL,
'display' => NULL,
'target_id' => 3,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPatchEntity() {
return array_diff_key($this->getNormalizedPostEntity(), ['field_media_file' => TRUE]);
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
switch ($method) {
case 'GET';
return "The 'view media' permission is required when the media item is published.";
case 'POST':
return "The following permissions are required: 'administer media' OR 'create media' OR 'create camelids media'.";
case 'PATCH':
return "The following permissions are required: 'update any media' OR 'update own media' OR 'camelids: edit any media' OR 'camelids: edit own media'.";
case 'DELETE':
return "The following permissions are required: 'delete any media' OR 'delete own media' OR 'camelids: delete any media' OR 'camelids: delete own media'.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
/**
* {@inheritdoc}
*/
public function testPost(): void {
$file_storage = $this->container->get('entity_type.manager')->getStorage('file');
// Step 1: upload file, results in File entity marked temporary.
$this->uploadFile();
$file = $file_storage->loadUnchanged(3);
$this->assertTrue($file->isTemporary());
$this->assertFalse($file->isPermanent());
// Step 2: create Media entity using the File, makes File entity permanent.
parent::testPost();
$file = $file_storage->loadUnchanged(3);
$this->assertFalse($file->isTemporary());
$this->assertTrue($file->isPermanent());
}
/**
* Tests the 'file_upload' REST resource plugin.
*
* This test duplicates some of the 'file_upload' REST resource plugin test
* coverage.
*
* @see \Drupal\Tests\rest\Functional\FileUploadResourceTestBase
*/
protected function uploadFile() {
// Enable the 'file_upload' REST resource for the current format + auth.
$this->resourceConfigStorage->create([
'id' => 'file.upload',
'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
'configuration' => [
'methods' => ['POST'],
'formats' => [static::$format],
'authentication' => isset(static::$auth) ? [static::$auth] : [],
],
'status' => TRUE,
])->save();
$this->refreshTestStateAfterRestConfigChange();
$this->initAuthentication();
// POST to create a File entity.
$url = Url::fromUri('base:file/upload/media/camelids/field_media_file');
$url->setOption('query', ['_format' => static::$format]);
$request_options = [];
$request_options[RequestOptions::HEADERS] = [
// Set the required (and only accepted) content type for the request.
'Content-Type' => 'application/octet-stream',
// Set the required Content-Disposition header for the file name.
'Content-Disposition' => 'file; filename="drupal rocks 🤘.txt"',
];
$request_options[RequestOptions::BODY] = 'Drupal is the best!';
$request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('POST'));
$response = $this->request('POST', $url, $request_options);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('POST'), $response);
// Grant necessary permission, retry.
$this->grantPermissionsToTestedRole(['create camelids media']);
$response = $this->request('POST', $url, $request_options);
$this->assertSame(201, $response->getStatusCode());
$expected = $this->getExpectedNormalizedFileEntity();
static::recursiveKSort($expected);
$actual = $this->serializer->decode((string) $response->getBody(), static::$format);
static::recursiveKSort($actual);
$this->assertSame($expected, $actual);
// Make sure the role save below properly invalidates cache tags.
$this->refreshVariables();
// To still run the complete test coverage for POSTing a Media entity, we
// must revoke the additional permissions that we granted.
$role = Role::load(static::$auth ? RoleInterface::AUTHENTICATED_ID : RoleInterface::ANONYMOUS_ID);
$role->revokePermission('create camelids media');
$role->trustData()->save();
}
/**
* Gets the expected file entity.
*
* @return array
* The expected normalized data array.
*/
protected function getExpectedNormalizedFileEntity() {
$file = File::load(3);
$owner = static::$auth ? $this->account : User::load(0);
return [
'fid' => [
[
'value' => 3,
],
],
'uuid' => [
[
'value' => $file->uuid(),
],
],
'langcode' => [
[
'value' => 'en',
],
],
'uid' => [
[
'target_id' => (int) $owner->id(),
'target_type' => 'user',
'target_uuid' => $owner->uuid(),
'url' => base_path() . 'user/' . $owner->id(),
],
],
'filename' => [
[
'value' => 'drupal rocks 🤘.txt',
],
],
'uri' => [
[
'value' => 'public://' . date('Y-m') . '/drupal rocks 🤘.txt',
'url' => base_path() . $this->siteDirectory . '/files/' . date('Y-m') . '/drupal%20rocks%20%F0%9F%A4%98.txt',
],
],
'filemime' => [
[
'value' => 'text/plain',
],
],
'filesize' => [
[
'value' => 19,
],
],
'status' => [
[
'value' => FALSE,
],
],
'created' => [
[
'value' => (new \DateTime())->setTimestamp($file->getCreatedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'changed' => [
[
'value' => (new \DateTime())->setTimestamp($file->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedEntityAccessCacheability($is_authenticated) {
// @see \Drupal\media\MediaAccessControlHandler::checkAccess()
return parent::getExpectedUnauthorizedEntityAccessCacheability($is_authenticated)
->addCacheTags(['media:1']);
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class MediaTypeJsonAnonTest extends MediaTypeResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class MediaTypeJsonBasicAuthTest extends MediaTypeResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class MediaTypeJsonCookieTest extends MediaTypeResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\media\Entity\MediaType;
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
abstract class MediaTypeResourceTestBase extends ConfigEntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'media_type';
/**
* @var \Drupal\media\MediaTypeInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer media types']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" media type.
$camelids = MediaType::create([
'label' => 'Camelids',
'id' => 'camelids',
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
'source' => 'file',
]);
$camelids->save();
return $camelids;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
'field_map' => [],
'id' => 'camelids',
'label' => 'Camelids',
'langcode' => 'en',
'source' => 'file',
'queue_thumbnail_downloads' => FALSE,
'new_revision' => FALSE,
'source_configuration' => [
'source_field' => '',
],
'status' => TRUE,
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
return [];
}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaTypeXmlAnonTest extends MediaTypeResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaTypeXmlBasicAuthTest extends MediaTypeResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaTypeXmlCookieTest extends MediaTypeResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaXmlAnonTest extends MediaResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaXmlBasicAuthTest extends MediaResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaXmlCookieTest extends MediaResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
use Drupal\media\Entity\MediaType;
use Drupal\media\Plugin\media\Source\File;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Tests update functions for the Media module.
*
* @group media
*/
class MediaMappingUpdateTest extends UpdatePathTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles(): void {
$this->databaseDumpFiles = [
$this->getDrupalRoot() . '/core/modules/system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz',
__DIR__ . '/../../../fixtures/update/media.php',
];
}
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = $this->container->get('entity_type.manager');
/** @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $update_manager */
$update_manager = $this->container->get('entity.definition_update_manager');
$entity_type = $entity_type_manager->getDefinition('media_type');
$update_manager->installEntityType($entity_type);
$entity_type = $entity_type_manager->getDefinition('media');
$update_manager->installEntityType($entity_type);
}
/**
* Tests updating Media types using source field in meta mappings.
*
* @see media_post_update_remove_mappings_targeting_source_field()
*/
public function testMediaMappingUpdate(): void {
$media_type = $this->createMediaType('image', ['id' => 'invalid_mapping']);
$source_field_name = $media_type->getSource()
->getSourceFieldDefinition($media_type)
->getName();
$field_map = $media_type->getFieldMap();
$field_map[File::METADATA_ATTRIBUTE_MIME] = $source_field_name;
$this->config($media_type->getConfigDependencyName())
->set('field_map', $field_map)
->save();
$this->runUpdates();
$this->container->get('entity_type.manager')
->getStorage('media_type')
->resetCache(['invalid_mapping']);
$field_map = MediaType::load('invalid_mapping')?->getFieldMap();
$this->assertIsArray($field_map);
$this->assertNotContains($source_field_name, $field_map);
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Update;
use Drupal\FunctionalTests\Update\UpdatePathTestBase;
use Drupal\Tests\UpdatePathTestTrait;
/**
* Tests update of media.settings:iframe_domain if it's still the default of "".
*
* @group system
* @covers \media_post_update_set_blank_iframe_domain_to_null
*/
class MediaSettingsDefaultIframeDomainUpdateTest extends UpdatePathTestBase {
use UpdatePathTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
DRUPAL_ROOT . '/core/modules/system/tests/fixtures/update/drupal-9.4.0.bare.standard.php.gz',
__DIR__ . '/../../../fixtures/update/media.php',
];
}
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Because the test manually installs media module, the entity type config
// must be manually installed similar to kernel tests.
$entity_type_manager = \Drupal::entityTypeManager();
$media = $entity_type_manager->getDefinition('media');
\Drupal::service('entity_type.listener')->onEntityTypeCreate($media);
$media_type = $entity_type_manager->getDefinition('media_type');
\Drupal::service('entity_type.listener')->onEntityTypeCreate($media_type);
}
/**
* Tests update of media.settings:iframe_domain.
*/
public function testUpdate(): void {
$iframe_domain_before = $this->config('media.settings')->get('iframe_domain');
$this->assertSame('', $iframe_domain_before);
$this->runUpdates();
$iframe_domain_after = $this->config('media.settings')->get('iframe_domain');
$this->assertNull($iframe_domain_after);
}
}

View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
// cspell:ignore dailymotion
/**
* Tests the oEmbed URL resolver service.
*
* @coversDefaultClass \Drupal\media\OEmbed\UrlResolver
*
* @group media
* @group #slow
*/
class UrlResolverTest extends MediaFunctionalTestBase {
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->lockHttpClientToFixtures();
$this->useFixtureProviders();
}
/**
* Data provider for testEndpointMatching().
*
* @see ::testEndpointMatching()
*
* @return array
*/
public static function providerEndpointMatching() {
return [
'match by endpoint: Twitter' => [
'https://twitter.com/Dries/status/999985431595880448',
'https://publish.twitter.com/oembed?url=https%3A//twitter.com/Dries/status/999985431595880448',
],
'match by endpoint: Vimeo' => [
'https://vimeo.com/14782834',
'https://vimeo.com/api/oembed.json?url=https%3A//vimeo.com/14782834',
],
'match by endpoint: Dailymotion' => [
'https://www.dailymotion.com/video/x2vzluh',
'https://www.dailymotion.com/services/oembed?url=https%3A//www.dailymotion.com/video/x2vzluh',
],
'match by endpoint: Facebook' => [
'https://www.facebook.com/facebook/videos/10153231379946729/',
'https://www.facebook.com/plugins/video/oembed.json?url=https%3A//www.facebook.com/facebook/videos/10153231379946729/',
],
];
}
/**
* Tests resource URL resolution with a matched provider endpoint.
*
* @covers ::getProviderByUrl
* @covers ::getResourceUrl
*
* @param string $url
* The asset URL to resolve.
* @param string $resource_url
* The expected oEmbed resource URL of the asset.
*
* @dataProvider providerEndpointMatching
*/
public function testEndpointMatching($url, $resource_url): void {
$this->assertSame(
$resource_url,
$this->container->get('media.oembed.url_resolver')->getResourceUrl($url)
);
}
/**
* Tests that hook_oembed_resource_url_alter() is invoked.
*
* @depends testEndpointMatching
*/
public function testResourceUrlAlterHook(): void {
$this->container->get('module_installer')->install(['media_test_oembed']);
$resource_url = $this->container->get('media.oembed.url_resolver')
->getResourceUrl('https://vimeo.com/14782834');
$this->assertStringContainsString('altered=1', parse_url($resource_url, PHP_URL_QUERY));
}
/**
* Data provider for testUrlDiscovery().
*
* @see ::testUrlDiscovery()
*
* @return array
*/
public static function providerUrlDiscovery() {
return [
'JSON resource' => [
'video_vimeo.html',
'https://vimeo.com/api/oembed.json?url=video_vimeo.html',
],
'XML resource' => [
'video_dailymotion.html',
'https://www.dailymotion.com/services/oembed?url=video_dailymotion.html',
],
];
}
/**
* Tests URL resolution when the URL is discovered by scanning the asset.
*
* @param string $url
* The asset URL to resolve.
* @param string $resource_url
* The expected oEmbed resource URL of the asset.
*
* @covers ::discoverResourceUrl
* @covers ::getProviderByUrl
* @covers ::getResourceUrl
*
* @dataProvider providerUrlDiscovery
*/
public function testUrlDiscovery($url, $resource_url): void {
$this->assertSame(
$this->container->get('media.oembed.url_resolver')->getResourceUrl($url),
$resource_url
);
}
}

View File

@@ -0,0 +1,208 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\InstallStorage;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media\Entity\Media;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* Basic display tests for Media.
*
* @group media
*/
class MediaDisplayTest extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Install the optional configs from the standard profile.
$extension_path = $this->container->get('extension.list.profile')->getPath('standard');
$optional_install_path = $extension_path . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
$storage = new FileStorage($optional_install_path);
$this->container->get('config.installer')->installOptionalConfig($storage, '');
// Reset all the static caches and list caches.
$this->container->get('config.factory')->reset();
// This test is going to test the display, so we need the standalone URL.
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
}
/**
* Tests basic media display.
*/
public function testMediaDisplay(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$media_type = $this->createMediaType('test');
// Create a media item.
$media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Fantastic!',
]);
$media->save();
$this->drupalGet('media/' . $media->id());
// Verify the "name" field is really not present. The name should be in the
// h1 with no additional markup in the h1.
$assert_session->elementTextContains('css', 'h1', $media->getName());
$assert_session->elementNotExists('css', 'h1 div');
// Enable the field on the display and verify it becomes visible on the UI.
$this->drupalGet("/admin/structure/media/manage/{$media_type->id()}/display");
$assert_session->buttonExists('Show row weights')->press();
$this->assertSession()->waitForElementVisible('css', '[name="fields[name][region]"]');
$page->selectFieldOption('fields[name][region]', 'content');
$assert_session->waitForElementVisible('css', '#edit-fields-name-settings-edit');
$page->pressButton('Save');
$this->drupalGet('media/' . $media->id());
// Verify the name is present, and its text matches what is expected. Now
// there should be markup in the h1.
$assert_session->elementTextContains('xpath', '//h1/div/div[1]', 'Name');
$assert_session->elementTextContains('xpath', '//h1/div/div[2]', $media->getName());
// In the standard profile, there are some pre-cooked types. Make sure the
// elements configured on their displays are the expected ones.
$this->drupalGet('media/add/image');
$image_media_name = 'example_1.jpeg';
$page->attachFileToField('files[field_media_image_0]', $this->root . '/core/modules/media/tests/fixtures/' . $image_media_name);
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->fillField('field_media_image[0][alt]', 'Image Alt Text 1');
$page->pressButton('Save');
$image_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$image_media_id = reset($image_media_id);
// Go to the media entity view.
$this->drupalGet('/media/' . $image_media_id);
// Check if the default media name is generated as expected.
$assert_session->elementTextContains('xpath', '//h1', $image_media_name);
// Here we expect to see only the image, nothing else.
// Assert only one element in the content region.
$media_item = $assert_session->elementExists('xpath', '//div[@class="layout-content"]/div/div[2]');
$assert_session->elementsCount('xpath', '/div', 1, $media_item);
// Assert the image is present inside the media element.
$media_image = $assert_session->elementExists('xpath', '//img', $media_item);
// Assert that the image src uses the large image style, the label is
// visually hidden, and there is no link to the image file.
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_image_src = $file_url_generator->generateString(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/example_1.jpeg'));
$this->assertStringContainsString($expected_image_src, $media_image->getAttribute('src'));
$field = $assert_session->elementExists('xpath', '/div[1]', $media_item);
$assert_session->elementExists('xpath', '/div[@class="visually-hidden"]', $field);
$assert_session->elementNotExists('xpath', '//a', $field);
$test_filename = $this->randomMachineName() . '.txt';
$test_filepath = 'public://' . $test_filename;
file_put_contents($test_filepath, $this->randomMachineName());
$this->drupalGet("media/add/document");
$page->attachFileToField("files[field_media_document_0]", \Drupal::service('file_system')->realpath($test_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
// Go to the media entity view.
$this->drupalGet($this->assertLinkToCreatedMedia());
// Check if the default media name is generated as expected.
$assert_session->elementTextContains('css', 'h1', $test_filename);
// Here we expect to see only the linked filename.
// Assert only one element in the content region.
$media_item = $assert_session->elementExists('xpath', '//div[@class="layout-content"]/div/div[2]');
$assert_session->elementsCount('xpath', '/div', 1, $media_item);
// Assert the file link is present, and its text matches the filename.
$link = $assert_session->elementExists('xpath', '//a', $media_item);
$this->assertSame($test_filename, $link->getText());
// Create a node type "page" to use as host entity.
$node_type = NodeType::create([
'type' => 'page',
'name' => 'Page',
]);
$node_type->save();
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$storage = FieldStorageConfig::create([
'entity_type' => 'node',
'field_name' => 'field_related_media',
'type' => 'entity_reference',
'settings' => [
'target_type' => 'media',
],
]);
$storage->save();
FieldConfig::create([
'field_storage' => $storage,
'entity_type' => 'node',
'bundle' => $node_type->id(),
'label' => 'Related media',
'settings' => [
'handler_settings' => [
'target_bundles' => [
'image' => 'image',
],
],
],
])->save();
\Drupal::service('entity_display.repository')->getViewDisplay('node', $node_type->id())
->setComponent('field_related_media', [
'type' => 'entity_reference_entity_view',
'label' => 'above',
'settings' => [
'view_mode' => 'full',
],
])->save();
$node = Node::create([
'title' => 'Host node',
'type' => $node_type->id(),
'field_related_media' => [
'target_id' => $image_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Media field (field_related_media) is there.
$assert_session->pageTextContains('Related media');
// Media name element is not there.
$assert_session->pageTextNotContains($image_media_name);
// Only one image is present.
$assert_session->elementsCount('xpath', '//img', 1);
// The image has the correct image style.
$assert_session->elementAttributeContains('xpath', '//img', 'src', '/styles/large/');
}
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
/**
* @covers ::media_filter_format_edit_form_validate
* @group media
* @group #slow
*/
class MediaEmbedFilterConfigurationUiAddTest extends MediaEmbedFilterTestBase {
/**
* @covers ::media_form_filter_format_add_form_alter
* @dataProvider providerTestValidations
*/
public function testValidationWhenAdding($filter_html_status, $filter_align_status, $filter_caption_status, $filter_html_image_secure_status, $media_embed, $allowed_html, $expected_error_message): void {
$this->drupalGet('admin/config/content/formats/add');
// Enable the `filter_html` and `media_embed` filters.
$page = $this->getSession()->getPage();
$page->fillField('name', 'Another test format');
$this->showHiddenFields();
$page->findField('format')->setValue('another_media_embed_test');
if ($filter_html_status) {
$page->checkField('filters[filter_html][status]');
}
if ($filter_align_status) {
$page->checkField('filters[filter_align][status]');
}
if ($filter_caption_status) {
$page->checkField('filters[filter_caption][status]');
}
if ($filter_html_image_secure_status) {
$page->checkField('filters[filter_html_image_secure][status]');
}
if ($media_embed === TRUE || is_numeric($media_embed)) {
$page->checkField('filters[media_embed][status]');
// Set a non-default weight.
if (is_numeric($media_embed)) {
$this->click('.tabledrag-toggle-weight');
$page->selectFieldOption('filters[media_embed][weight]', $media_embed);
}
}
if (!empty($allowed_html)) {
$page->clickLink('Limit allowed HTML tags and correct faulty HTML');
$page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html);
}
$page->pressButton('Save configuration');
if ($expected_error_message) {
$this->assertSession()->pageTextNotContains('Added text format Another test format.');
$this->assertSession()->pageTextContains($expected_error_message);
}
else {
$this->assertSession()->pageTextContains('Added text format Another test format.');
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
/**
* @covers ::media_filter_format_edit_form_validate
* @group media
* @group #slow
*/
class MediaEmbedFilterConfigurationUiEditTest extends MediaEmbedFilterTestBase {
/**
* @covers ::media_form_filter_format_edit_form_alter
* @dataProvider providerTestValidations
*/
public function testValidationWhenEditing($filter_html_status, $filter_align_status, $filter_caption_status, $filter_html_image_secure_status, $media_embed, $allowed_html, $expected_error_message): void {
$this->drupalGet('admin/config/content/formats/manage/media_embed_test');
// Enable the `filter_html` and `media_embed` filters.
$page = $this->getSession()->getPage();
if ($filter_html_status) {
$page->checkField('filters[filter_html][status]');
}
if ($filter_align_status) {
$page->checkField('filters[filter_align][status]');
}
if ($filter_caption_status) {
$page->checkField('filters[filter_caption][status]');
}
if ($filter_html_image_secure_status) {
$page->checkField('filters[filter_html_image_secure][status]');
}
if ($media_embed === TRUE || is_numeric($media_embed)) {
$page->checkField('filters[media_embed][status]');
// Set a non-default weight.
if (is_numeric($media_embed)) {
$this->click('.tabledrag-toggle-weight');
$page->selectFieldOption('filters[media_embed][weight]', $media_embed);
}
}
if (!empty($allowed_html)) {
$page->clickLink('Limit allowed HTML tags and correct faulty HTML');
$page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html);
}
$page->pressButton('Save configuration');
if ($expected_error_message) {
$this->assertSession()->pageTextNotContains('The text format Test format has been updated.');
$this->assertSession()->pageTextContains($expected_error_message);
}
else {
$this->assertSession()->pageTextContains('The text format Test format has been updated.');
}
}
}

View File

@@ -0,0 +1,176 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\filter\Entity\FilterFormat;
/**
* Base class for media embed filter configuration tests.
*/
class MediaEmbedFilterTestBase extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*
* @todo Remove this class property in https://www.drupal.org/node/3091878/.
*/
protected $failOnJavascriptConsoleErrors = FALSE;
/**
* {@inheritdoc}
*/
public static function setUpBeforeClass(): void {
parent::setUpBeforeClass();
// Necessary for @covers to work.
require_once __DIR__ . '/../../../media.module';
}
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$format = FilterFormat::create([
'format' => 'media_embed_test',
'name' => 'Test format',
'filters' => [],
]);
$format->save();
$this->drupalLogin($this->drupalCreateUser([
'administer filters',
$format->getPermissionName(),
]));
}
/**
* Data provider for testing validation when adding and editing media embeds.
*/
public static function providerTestValidations(): array {
return [
'Tests that no filter_html occurs when filter_html not enabled.' => [
'filter_html_status' => FALSE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => TRUE,
'allowed_html' => FALSE,
'expected_error_message' => FALSE,
],
'Tests validation when both filter_html and media_embed are disabled.' => [
'filter_html_status' => FALSE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => FALSE,
'allowed_html' => FALSE,
'expected_error_message' => FALSE,
],
'Tests validation when media_embed filter not enabled and filter_html is enabled.' => [
'filter_html_status' => TRUE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => FALSE,
'allowed_html' => 'default',
'expected_error_message' => FALSE,
],
'Tests validation when drupal-media element has no attributes.' => [
'filter_html_status' => TRUE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => TRUE,
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media>",
'expected_error_message' => 'The <drupal-media> tag in the allowed HTML tags is missing the following attributes: data-entity-type, data-entity-uuid.',
],
'Tests validation when drupal-media element lacks some required attributes.' => [
'filter_html_status' => TRUE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => TRUE,
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media data-entity-uuid data-align>",
'expected_error_message' => 'The <drupal-media> tag in the allowed HTML tags is missing the following attributes: data-entity-type.',
],
'Tests validation when both filter_html and media_embed are enabled and configured correctly' => [
'filter_html_status' => TRUE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => TRUE,
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media data-entity-type data-entity-uuid data-view-mode>",
'expected_error_message' => FALSE,
],
'Order validation: media_embed before all filters' => [
'filter_html_status' => TRUE,
'filter_align_status' => TRUE,
'filter_caption_status' => TRUE,
'filter_html_image_secure_status' => TRUE,
'media_embed' => '-5',
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media data-entity-type data-entity-uuid data-view-mode>",
'expected_error_message' => 'The Embed media filter needs to be placed after the following filters: Align images, Caption images, Restrict images to this site.',
],
'Order validation: media_embed before filter_align' => [
'filter_html_status' => FALSE,
'filter_align_status' => TRUE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => '-5',
'allowed_html' => '',
'expected_error_message' => 'The Embed media filter needs to be placed after the Align images filter.',
],
'Order validation: media_embed before filter_caption' => [
'filter_html_status' => FALSE,
'filter_align_status' => FALSE,
'filter_caption_status' => TRUE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => '-5',
'allowed_html' => '',
'expected_error_message' => 'The Embed media filter needs to be placed after the Caption images filter.',
],
'Order validation: media_embed before filter_html_image_secure' => [
'filter_html_status' => FALSE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => TRUE,
'media_embed' => '-5',
'allowed_html' => '',
'expected_error_message' => 'The Embed media filter needs to be placed after the Restrict images to this site filter.',
],
'Order validation: media_embed after filter_align and filter_caption but before filter_html_image_secure' => [
'filter_html_status' => TRUE,
'filter_align_status' => TRUE,
'filter_caption_status' => TRUE,
'filter_html_image_secure_status' => TRUE,
'media_embed' => '5',
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media data-entity-type data-entity-uuid data-view-mode>",
'expected_error_message' => 'The Embed media filter needs to be placed after the Restrict images to this site filter.',
],
];
}
/**
* Show visually hidden fields.
*/
protected function showHiddenFields() {
$script = <<<JS
var hidden_fields = document.querySelectorAll(".hidden");
[].forEach.call(hidden_fields, function(el) {
el.classList.remove("hidden");
});
JS;
$this->getSession()->executeScript($script);
}
}

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\media\Functional\MediaFunctionalTestTrait;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Base class for Media functional JavaScript tests.
*/
abstract class MediaJavascriptTestBase extends WebDriverTestBase {
use MediaFunctionalTestTrait;
use MediaTypeCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
protected 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);
}
/**
* Asserts that a link to a new media item is displayed in the messages area.
*
* @return string
* The link URL.
*/
protected function assertLinkToCreatedMedia() {
$assert_session = $this->assertSession();
$selector = 'div[aria-label="Status message"] a';
// Get the canonical media entity URL from the creation message.
$link = $assert_session->elementExists('css', $selector);
$assert_session->elementAttributeExists('css', $selector, 'href');
return $link->getAttribute('href');
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
/**
* Tests related to media reference fields.
*
* @group media
*/
class MediaReferenceFieldHelpTest extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = [
'media',
'media_library',
];
/**
* Tests our custom help texts when creating a field.
*
* @see media_form_field_ui_field_storage_add_form_alter()
*/
public function testFieldCreationHelpText(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$type = $this->drupalCreateContentType([
'type' => 'foo',
]);
$this->drupalGet("/admin/structure/types/manage/{$type->id()}/fields/add-field");
$field_groups = [
'file_upload',
'field_ui:entity_reference:media',
];
$help_text = 'Use Media reference fields for most files, images, audio, videos, and remote media. Use File or Image reference fields when creating your own media types, or for legacy files and images created before installing the Media module.';
// Choose a boolean field, none of the description containers should be
// visible.
$assert_session->elementExists('css', "[name='new_storage_type'][value='boolean']");
$page->find('css', "[name='new_storage_type'][value='boolean']")->getParent()->click();
$page->pressButton('Continue');
$assert_session->pageTextNotContains($help_text);
$page->pressButton('Back');
// Select each of the Reference, File upload field groups and verify their
// descriptions are now visible and match the expected text.
foreach ($field_groups as $field_group) {
$assert_session->elementExists('css', "[name='new_storage_type'][value='$field_group']");
$page->find('css', "[name='new_storage_type'][value='$field_group']")->getParent()->click();
$page->pressButton('Continue');
$assert_session->pageTextContains($help_text);
$page->pressButton('Back');
}
}
}

View File

@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
/**
* Tests the Audio and Video media sources.
*
* @group media
*/
class MediaSourceAudioVideoTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Check the Audio source functionality.
*/
public function testAudioTypeCreation(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_id = 'audio_file';
$type_name = 'audio_type';
$field_name = 'field_media_' . $source_id;
$this->doTestCreateMediaType($type_name, $source_id);
// Check that the source field was created with the correct settings.
$storage = FieldStorageConfig::load("media.$field_name");
$this->assertInstanceOf(FieldStorageConfig::class, $storage);
$field = FieldConfig::load("media.$type_name.$field_name");
$this->assertInstanceOf(FieldConfig::class, $field);
$this->assertSame('mp3 wav aac', FieldConfig::load("media.$type_name.$field_name")->get('settings')['file_extensions']);
// Check that the display holds the correct formatter configuration.
$display = EntityViewDisplay::load("media.$type_name.default");
$this->assertInstanceOf(EntityViewDisplay::class, $display);
$formatter = $display->getComponent($field_name)['type'];
$this->assertSame('file_audio', $formatter);
// Create a media asset.
file_put_contents('public://file.mp3', str_repeat('t', 10));
$file = File::create([
'uri' => 'public://file.mp3',
'filename' => 'file.mp3',
]);
$file->save();
$this->drupalGet("media/add/$type_name");
$page->fillField('Name', 'Audio media asset');
$page->attachFileToField("files[{$field_name}_0]", \Drupal::service('file_system')->realpath('public://file.mp3'));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
// Verify that there is a creation message and that it contains a link to
// the media entity.
$assert_session->pageTextContains("$type_name Audio media asset has been created.");
$this->drupalGet($this->assertLinkToCreatedMedia());
// Verify that the <audio> tag is present on the media entity view.
$assert_session->elementExists('css', "audio > source[type='audio/mpeg']");
}
/**
* Check the Video source functionality.
*/
public function testVideoTypeCreation(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_id = 'video_file';
$type_name = 'video_type';
$field_name = 'field_media_' . $source_id;
$this->doTestCreateMediaType($type_name, $source_id);
// Check that the source field was created with the correct settings.
$storage = FieldStorageConfig::load("media.$field_name");
$this->assertInstanceOf(FieldStorageConfig::class, $storage);
$field = FieldConfig::load("media.$type_name.$field_name");
$this->assertInstanceOf(FieldConfig::class, $field);
$this->assertSame('mp4', FieldConfig::load("media.$type_name.$field_name")->getSetting('file_extensions'));
// Check that the display holds the correct formatter configuration.
$display = EntityViewDisplay::load("media.$type_name.default");
$this->assertInstanceOf(EntityViewDisplay::class, $display);
$formatter = $display->getComponent($field_name)['type'];
$this->assertSame('file_video', $formatter);
// Create a media asset.
file_put_contents('public://file.mp4', str_repeat('t', 10));
$file = File::create([
'uri' => 'public://file.mp4',
'filename' => 'file.mp4',
]);
$file->save();
$this->drupalGet("media/add/$type_name");
$page->fillField('Name', 'Video media asset');
$page->attachFileToField("files[{$field_name}_0]", \Drupal::service('file_system')->realpath('public://file.mp4'));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
// Verify that there is a creation message and that it contains a link to
// the media entity.
$assert_session->pageTextContains("$type_name Video media asset has been created.");
$this->drupalGet($this->assertLinkToCreatedMedia());
// Verify that the <video> tag is present on the media entity view.
$assert_session->elementExists('css', "video > source[type='video/mp4']");
}
}

View File

@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\media\Entity\Media;
use Drupal\media\Plugin\media\Source\File;
/**
* Tests the file media source.
*
* @group media
*/
class MediaSourceFileTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the file media source.
*/
public function testMediaFileSource(): void {
// Skipped due to frequent random test failures.
$this->markTestSkipped();
$media_type_id = 'test_media_file_type';
$source_field_id = 'field_media_file';
$provided_fields = [
File::METADATA_ATTRIBUTE_NAME,
File::METADATA_ATTRIBUTE_SIZE,
File::METADATA_ATTRIBUTE_MIME,
];
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->doTestCreateMediaType($media_type_id, 'file', $provided_fields);
// Create custom fields for the media type to store metadata attributes.
$fields = [
'field_string_file_size' => 'string',
'field_string_mime_type' => '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[" . File::METADATA_ATTRIBUTE_NAME . "]", 'name');
$page->selectFieldOption("field_map[" . File::METADATA_ATTRIBUTE_SIZE . "]", 'field_string_file_size');
$page->selectFieldOption("field_map[" . File::METADATA_ATTRIBUTE_MIME . "]", 'field_string_mime_type');
$page->pressButton('Save');
$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('admin/content/media');
// Get the media entity view URL from the creation message.
$this->drupalGet($this->assertLinkToCreatedMedia());
// Make sure a link to the file is displayed.
$assert_session->linkExists($test_filename);
// The thumbnail should not be displayed.
$assert_session->elementNotExists('css', 'img');
// 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 that all the fields are properly populated.
$media = Media::load(1);
$this->assertSame($test_filename, $media->getName());
$this->assertSame('8', $media->get('field_string_file_size')->value);
$this->assertSame('text/plain', $media->get('field_string_mime_type')->value);
// Test the MIME type icon.
$icon_base = \Drupal::config('media.settings')->get('icon_base_uri');
\Drupal::service('file_system')->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', 'img', 'src', 'text--plain.png');
// Check if the mapped name is automatically updated.
$new_filename = $this->randomMachineName() . '.txt';
$new_filepath = 'public://' . $new_filename;
file_put_contents($new_filepath, $this->randomMachineName());
$this->drupalGet("media/1/edit");
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($new_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
/** @var \Drupal\media\MediaInterface $media */
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged(1);
$this->assertEquals($new_filename, $media->getName());
$assert_session->statusMessageContains("$new_filename has been updated.", 'status');
}
}

View File

@@ -0,0 +1,193 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\image\Entity\ImageStyle;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\Plugin\media\Source\Image;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the image media source.
*
* @group media
*/
class MediaSourceImageTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the image media source.
*/
public function testMediaImageSource(): void {
$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]", $this->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('admin/content/media');
// Get the media entity view URL from the creation message.
$this->drupalGet($this->assertLinkToCreatedMedia());
// Assert the image element is present inside the media element and that its
// src attribute uses the large image style, the label is visually hidden,
// and there is no link to the image file.
$label = $assert_session->elementExists('xpath', '//div[contains(@class, "visually-hidden") and text()="Image"]');
// The field is the parent div of the label.
$field = $label->getParent();
$image_element = $field->find('css', 'img');
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_image_src = $file_url_generator->generateString(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/example_1.jpeg'));
$this->assertStringContainsString($expected_image_src, $image_element->getAttribute('src'));
$assert_session->elementNotExists('css', 'a', $field);
// Ensure the image has the correct alt attribute.
$this->assertSame('Image Alt Text 1', $image_element->getAttribute('alt'));
// Load the media and check that all fields are properly populated.
$media = Media::load(1);
$this->assertSame('example_1.jpeg', $media->getName());
$this->assertSame('200', $media->get('field_string_width')->value);
$this->assertSame('89', $media->get('field_string_height')->value);
// Tests the warning when the default display's image style is missing.
$this->drupalLogin($this->drupalCreateUser([
'administer site configuration',
'access media overview',
'administer media',
'administer media types',
'administer media display',
'view media',
// We need 'access content' for system.machine_name_transliterate.
'access content',
]));
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
// If for some reason a site builder deletes the 'large' image style, do
// not add an image style to the new entity view display's image field.
// Instead, add a warning on the 'Status report' page.
ImageStyle::load('large')->delete();
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', 'Ada Lovelace');
$this->assertNotEmpty($assert_session->waitForText('Machine name: ada_lovelace'));
$page->selectFieldOption('source', 'image');
// Wait for the form to complete with AJAX.
$this->assertNotEmpty($assert_session->waitForText('Field mapping'));
$page->pressButton('Save');
$this->assertViewDisplayConfigured('ada_lovelace');
// Create user without the 'administer media display' permission.
$this->drupalLogin($this->drupalCreateUser([
'administer site configuration',
'access media overview',
'administer media',
'administer media types',
'view media',
// We need 'access content' for system.machine_name_transliterate.
'access content',
]));
// Test that hook_requirements adds warning about the lack of an image
// style.
$this->drupalGet('/admin/reports/status');
// The image style warning should not include an action link when the
// current user lacks the permission 'administer media display'.
$assert_session->pageTextContains('The default display for the Ada Lovelace media type is not currently using an image style on the Image field. Not using an image style can lead to much larger file downloads.');
$assert_session->linkNotExists('add an image style to the Image field');
$assert_session->linkByHrefNotExists('/admin/structure/media/manage/ada_lovelace/display');
// The image style warning should include an action link when the current
// user has the permission 'administer media display'.
Role::load(RoleInterface::AUTHENTICATED_ID)
->grantPermission('administer media display')
->save();
$this->drupalGet('/admin/reports/status');
$assert_session->pageTextContains('The default display for the Ada Lovelace media type is not currently using an image style on the Image field. Not using an image style can lead to much larger file downloads. If you would like to change this, add an image style to the Image field.');
$assert_session->linkExists('add an image style to the Image field');
$assert_session->linkByHrefExists('/admin/structure/media/manage/ada_lovelace/display');
// The image style warning should not include an action link when the
// Field UI module is uninstalled.
$this->container->get('module_installer')->uninstall(['field_ui']);
$this->drupalGet('/admin/reports/status');
$assert_session->pageTextContains('The default display for the Ada Lovelace media type is not currently using an image style on the Image field. Not using an image style can lead to much larger file downloads.');
$assert_session->linkNotExists('add an image style to the Image field');
$assert_session->linkByHrefNotExists('/admin/structure/media/manage/ada_lovelace/display');
}
/**
* Asserts the proper entity view display components for a media type.
*
* @param string $media_type_id
* The media type ID.
*
* @internal
*/
protected function assertViewDisplayConfigured(string $media_type_id): void {
$assert_session = $this->assertSession();
$type = MediaType::load($media_type_id);
$display = EntityViewDisplay::load('media.' . $media_type_id . '.' . EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE);
$this->assertInstanceOf(EntityViewDisplay::class, $display);
$source_field_definition = $type->getSource()->getSourceFieldDefinition($type);
$component = $display->getComponent($source_field_definition->getName());
$this->assertSame('visually_hidden', $component['label']);
if (ImageStyle::load('large')) {
$this->assertSame('large', $component['settings']['image_style']);
}
else {
$this->assertEmpty($component['settings']['image_style']);
}
$this->assertEmpty($component['settings']['image_link']);
// Since components that aren't explicitly hidden can show up on the
// display edit form, check that only the image field appears enabled on
// the display edit form.
$this->drupalGet('/admin/structure/media/manage/' . $media_type_id . '/display');
// Assert that only the source field is enabled.
$assert_session->elementExists('css', 'input[name="' . $source_field_definition->getName() . '_settings_edit"]');
$assert_session->elementsCount('css', 'input[name$="_settings_edit"]', 1);
}
}

View File

@@ -0,0 +1,280 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Database\Database;
use Drupal\dblog\Controller\DbLogController;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media_test_oembed\Controller\ResourceController;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
use Drupal\user\Entity\Role;
use Symfony\Component\DependencyInjection\ContainerInterface;
// cspell:ignore dailymotion Schipulcon
/**
* Tests the oembed:video media source.
*
* @group media
*/
class MediaSourceOEmbedVideoTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media_test_oembed', 'dblog'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->lockHttpClientToFixtures();
}
/**
* {@inheritdoc}
*/
protected function initConfig(ContainerInterface $container) {
parent::initConfig($container);
// Enable twig debugging to make testing template usage easy.
$parameters = $container->getParameter('twig.config');
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
}
/**
* Tests the oembed media source.
*/
public function testMediaOEmbedVideoSource(): void {
$media_type_id = 'test_media_oembed_type';
$provided_fields = [
'type',
'title',
'default_name',
'author_name',
'author_url',
'provider_name',
'provider_url',
'cache_age',
'thumbnail_uri',
'thumbnail_width',
'thumbnail_height',
'url',
'width',
'height',
'html',
];
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->doTestCreateMediaType($media_type_id, 'oembed:video', $provided_fields);
// Create custom fields for the media type to store metadata attributes.
$fields = [
'field_string_width' => 'string',
'field_string_height' => 'string',
'field_string_author_name' => '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");
// Only accept Vimeo videos.
$page->checkField("source_configuration[providers][Vimeo]");
$assert_session->selectExists('field_map[width]')->setValue('field_string_width');
$assert_session->selectExists('field_map[height]')->setValue('field_string_height');
$assert_session->selectExists('field_map[author_name]')->setValue('field_string_author_name');
$assert_session->buttonExists('Save')->press();
// Configure the iframe to be narrower than the actual video, so we can
// verify that the video scales correctly.
$display = \Drupal::service('entity_display.repository')->getViewDisplay('media', $media_type_id);
$this->assertFalse($display->isNew());
$component = $display->getComponent('field_media_oembed_video');
$this->assertIsArray($component);
$component['settings']['max_width'] = 240;
$display->setComponent('field_media_oembed_video', $component);
$this->assertSame(SAVED_UPDATED, $display->save());
$this->hijackProviderEndpoints();
$video_url = 'https://vimeo.com/7073899';
ResourceController::setResourceUrl($video_url, $this->getFixturesDirectory() . '/video_vimeo.json');
// Create a media item.
$this->drupalGet("media/add/$media_type_id");
$assert_session->fieldExists('Remote video URL')->setValue($video_url);
$assert_session->buttonExists('Save')->press();
$assert_session->addressEquals('admin/content/media');
// Get the media entity view URL from the creation message.
$this->drupalGet($this->assertLinkToCreatedMedia());
/** @var \Drupal\media\MediaInterface $media */
$media = Media::load(1);
// The thumbnail should have been downloaded.
$thumbnail = $media->getSource()->getMetadata($media, 'thumbnail_uri');
$this->assertFileExists($thumbnail);
// Ensure the iframe exists and has the expected CSS class, and that its src
// attribute contains a coherent URL with the query parameters we expect.
$iframe = $assert_session->elementExists('css', 'iframe.media-oembed-content');
$iframe_url = parse_url($iframe->getAttribute('src'));
$this->assertStringEndsWith('/media/oembed', $iframe_url['path']);
$this->assertNotEmpty($iframe_url['query']);
$query = [];
parse_str($iframe_url['query'], $query);
$this->assertSame($video_url, $query['url']);
$this->assertNotEmpty($query['hash']);
// Ensure that the outer iframe's width respects the formatter settings.
$this->assertSame('480', $iframe->getAttribute('width'));
// Check the inner iframe to make sure that CSS has been applied to scale it
// correctly, regardless of whatever its width attribute may be (the fixture
// hard-codes it to 480).
$inner_frame = 'frames[0].document.querySelector("iframe")';
$this->assertSame('480', $session->evaluateScript("$inner_frame.getAttribute('width')"));
$this->assertLessThanOrEqual(240, $session->evaluateScript("$inner_frame.clientWidth"));
// The oEmbed content iFrame should be visible.
$assert_session->elementExists('css', 'iframe.media-oembed-content');
// The thumbnail should not be displayed.
$assert_session->elementNotExists('css', 'img');
// Load the media and check that all fields are properly populated.
$media = Media::load(1);
$this->assertSame('Drupal Rap Video - Schipulcon09', $media->getName());
$this->assertSame('480', $media->field_string_width->value);
$this->assertSame('360', $media->field_string_height->value);
// Try to create a media asset from a disallowed provider.
$this->drupalGet("media/add/$media_type_id");
$assert_session->fieldExists('Remote video URL')->setValue('https://www.dailymotion.com/video/x2vzluh');
$page->pressButton('Save');
$assert_session->pageTextContains('The Dailymotion provider is not allowed.');
// Register a Dailymotion video as a second oEmbed resource. Note that its
// thumbnail URL does not have a file extension.
$media_type = MediaType::load($media_type_id);
$source_configuration = $media_type->getSource()->getConfiguration();
$source_configuration['providers'][] = 'Dailymotion';
$media_type->getSource()->setConfiguration($source_configuration);
$media_type->save();
$video_url = 'https://www.dailymotion.com/video/x2vzluh';
ResourceController::setResourceUrl($video_url, $this->getFixturesDirectory() . '/video_dailymotion.xml');
// Create a new media item using a Dailymotion video.
$this->drupalGet("media/add/$media_type_id");
$assert_session->fieldExists('Remote video URL')->setValue($video_url);
$assert_session->buttonExists('Save')->press();
/** @var \Drupal\media\MediaInterface $media */
$media = Media::load(2);
$thumbnail = $media->getSource()->getMetadata($media, 'thumbnail_uri');
$this->assertFileExists($thumbnail);
// Although the resource's thumbnail URL doesn't have a file extension, we
// should have deduced the correct one.
$this->assertStringEndsWith('.png', $thumbnail);
// Test ResourceException logging.
$video_url = 'https://vimeo.com/1111';
ResourceController::setResourceUrl($video_url, $this->getFixturesDirectory() . '/video_vimeo.json');
$this->drupalGet("media/add/$media_type_id");
$assert_session->fieldExists('Remote video URL')->setValue($video_url);
$assert_session->buttonExists('Save')->press();
$assert_session->addressEquals('admin/content/media');
ResourceController::setResource404($video_url);
$this->drupalGet($this->assertLinkToCreatedMedia());
$row = Database::getConnection()->select('watchdog')
->fields('watchdog', ['message', 'variables'])
->orderBy('wid', 'DESC')
->range(0, 1)
->execute()
->fetchObject();
$message = (string) DbLogController::create($this->container)->formatMessage($row);
$this->assertStringContainsString('resulted in a `404 Not Found` response', $message);
// Test anonymous access to media via iframe.
$this->drupalLogout();
// Without a hash should be denied.
$no_hash_query = array_diff_key($query, ['hash' => '']);
$this->drupalGet('media/oembed', ['query' => $no_hash_query]);
$assert_session->pageTextNotContains('Vimeo works!');
$assert_session->pageTextContains('Client error');
// A correct query should be allowed because the anonymous role has the
// 'view media' permission.
$this->drupalGet('media/oembed', ['query' => $query]);
$assert_session->pageTextContains('Vimeo works!');
// Remove the 'view media' permission to test that this restricts access.
$role = Role::load(AccountInterface::ANONYMOUS_ROLE);
$role->revokePermission('view media');
$role->save();
$this->drupalGet('media/oembed', ['query' => $query]);
$assert_session->pageTextNotContains('Vimeo works!');
$assert_session->pageTextContains('Access denied');
}
/**
* Tests that a security warning appears if iFrame domain is not set.
*/
public function testOEmbedSecurityWarning(): void {
$media_type_id = 'test_media_oembed_type';
$source_id = 'oembed:video';
$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);
$assert_session->pageTextContains('It is potentially insecure to display oEmbed content in a frame');
$this->config('media.settings')->set('iframe_domain', 'http://example.com')->save();
$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);
$assert_session->pageTextNotContains('It is potentially insecure to display oEmbed content in a frame');
}
}

View File

@@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
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 {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Let's set the canonical flag in the base class of the source tests,
// because every source test has to check the output on the view page.
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
}
/**
* 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\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$entity_form_display = $display_repository->getFormDisplay('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, []);
$entity_display = $display_repository->getViewDisplay('media', $media_type_id);
$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\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity_form_display */
$entity_form_display = $display_repository->getFormDisplay('media', $media_type_id, 'default');
if ($entity_form_display->getComponent($field_name)) {
$entity_form_display->removeComponent($field_name)->save();
}
}
/**
* Tests 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.
* @param string $source_label_visibility
* (optional) The visibility that the source field label is expected to
* have. Defaults to 'visually_hidden'.
*
* @return \Drupal\media\MediaTypeInterface
* The created media type.
*/
public function doTestCreateMediaType($media_type_id, $source_id, array $provided_fields = [], $source_label_visibility = 'visually_hidden') {
$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);
$media_type = MediaType::load($media_type_id);
// Assert that the default display of the media type only shows the source
// field.
$this->drupalGet("/admin/structure/media/manage/$media_type_id/display");
// There should be only one field with editable settings, and it should be
// the source field.
$assert_session->elementsCount('css', 'input[name$="_settings_edit"]', 1);
$source_field_name = $media_type->getSource()
->getSourceFieldDefinition($media_type)
->getName();
$assert_session->buttonExists("{$source_field_name}_settings_edit");
// Ensure the source field label is configured as expected.
$assert_session->fieldValueEquals("fields[$source_field_name][label]", $source_label_visibility);
// 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 $media_type;
}
}

View File

@@ -0,0 +1,572 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media_test_oembed\Controller\ResourceController;
use Drupal\node\Entity\Node;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
// cspell:ignore Drupalin Hustlin Schipulcon
/**
* Basic tests for Media configuration in the standard profile.
*
* @group media
*/
class MediaStandardProfileTest extends MediaJavascriptTestBase {
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected $profile = 'standard';
/**
* {@inheritdoc}
*/
protected static $modules = ['media_test_oembed'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->lockHttpClientToFixtures();
$this->hijackProviderEndpoints();
}
/**
* Tests all media sources in one method.
*
* This prevents installing the standard profile for every test case and
* increases the performance of this test.
*/
public function testMediaSources(): void {
// This test currently frequently causes the SQLite database to lock, so
// skip the test on SQLite until the issue can be resolved.
// @todo https://www.drupal.org/project/drupal/issues/3273626
if (Database::getConnection()->driver() === 'sqlite') {
$this->markTestSkipped('Test frequently causes a locked database on SQLite');
}
$storage = FieldStorageConfig::create([
'entity_type' => 'node',
'field_name' => 'field_related_media',
'type' => 'entity_reference',
'settings' => [
'target_type' => 'media',
],
]);
$storage->save();
FieldConfig::create([
'field_storage' => $storage,
'entity_type' => 'node',
'bundle' => 'article',
'label' => 'Related media',
'settings' => [
'handler_settings' => [
'target_bundles' => [
'audio' => 'audio',
'document' => 'document',
'image' => 'image',
'remote_video' => 'remote_video',
'video' => 'video',
],
],
],
])->save();
$display = EntityViewDisplay::load('node.article.default');
$display->setComponent('field_related_media', [
'type' => 'entity_reference_entity_view',
'settings' => [
'view_mode' => 'full',
],
])->save();
$this->audioTest();
$this->documentTest();
$this->imageTest();
$this->remoteVideoTest();
$this->videoTest();
}
/**
* Tests the standard profile configuration for media type 'audio'.
*/
protected function audioTest() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_audio_file';
// Create 2 test files.
$test_filename = $this->randomMachineName() . '.mp3';
$test_filepath = 'public://' . $test_filename;
$test_filename_updated = $this->randomMachineName() . '.mp3';
$test_filepath_updated = 'public://' . $test_filename_updated;
file_put_contents($test_filepath, str_repeat('t', 10));
file_put_contents($test_filepath_updated, str_repeat('u', 10));
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/audio');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("files[{$source_field_id}_0]");
// Create a media item.
$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');
$audio_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$audio_media_id = reset($audio_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $audio_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($audio_media_id);
$this->assertSame($test_filename, $media->label());
// Here we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-audio > *', 1);
// Assert the audio file is present inside the media element and that its
// src attribute matches the audio file.
$audio_element = $assert_session->elementExists('css', 'div.media--type-audio .field--name-field-media-audio-file audio > source');
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_audio_src = $file_url_generator->generateString(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename));
$this->assertSame($expected_audio_src, $audio_element->getAttribute('src'));
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $audio_media_id . '/edit');
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath_updated));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($audio_media_id);
$this->assertSame($test_filename_updated, $media->label());
// Again we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-audio > *', 1);
// Assert the audio file is present inside the media element and that its
// src attribute matches the updated audio file.
$audio_element = $assert_session->elementExists('css', 'div.media--type-audio .field--name-field-media-audio-file audio > source');
$expected_audio_src = $file_url_generator->generateString(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename_updated));
$this->assertSame($expected_audio_src, $audio_element->getAttribute('src'));
}
/**
* Tests the standard profile configuration for media type 'image'.
*/
protected function imageTest() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_image';
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/image');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("files[{$source_field_id}_0]");
// Create a media item.
$image_media_name = 'example_1.jpeg';
$page->attachFileToField("files[{$source_field_id}_0]", $this->root . '/core/modules/media/tests/fixtures/' . $image_media_name);
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->fillField("{$source_field_id}[0][alt]", 'Image Alt Text 1');
$page->pressButton('Save');
$image_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$image_media_id = reset($image_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $image_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($image_media_id);
$this->assertSame($image_media_name, $media->label());
// Here we expect to see only the image, nothing else. Assert only one
// element in the content region.
$assert_session->elementsCount('css', 'div.media--type-image > *', 1);
// Assert the image element is present inside the media element and that its
// src attribute uses the large image style, the label is visually hidden,
// and there is no link to the image file.
$image_element = $assert_session->elementExists('css', 'div.media--type-image img');
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_image_src = $file_url_generator->generateString(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/' . $image_media_name));
$this->assertStringContainsString($expected_image_src, $image_element->getAttribute('src'));
$assert_session->elementExists('css', '.field--name-field-media-image .field__label.visually-hidden');
$assert_session->elementNotExists('css', '.field--name-field-media-image a');
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $image_media_id . '/edit');
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$image_media_name_updated = 'example_2.jpeg';
$page->attachFileToField("files[{$source_field_id}_0]", $this->root . '/core/modules/media/tests/fixtures/' . $image_media_name_updated);
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->fillField("{$source_field_id}[0][alt]", 'Image Alt Text 2');
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($image_media_id);
$this->assertSame($image_media_name_updated, $media->label());
// Again we expect to see only the image, nothing else. Assert only one
// element in the content region.
$assert_session->elementsCount('css', 'div.media--type-image > *', 1);
// Assert the image element is present inside the media element and that its
// src attribute uses the large image style, the label is visually hidden,
// and there is no link to the image file.
$image_element = $assert_session->elementExists('css', 'div.media--type-image img');
$expected_image_src = $file_url_generator->generateString(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/' . $image_media_name_updated));
$this->assertStringContainsString($expected_image_src, $image_element->getAttribute('src'));
$assert_session->elementExists('css', '.field--name-field-media-image .field__label.visually-hidden');
$assert_session->elementNotExists('css', '.field--name-field-media-image a');
}
/**
* Tests the standard profile configuration for media type 'document'.
*/
protected function documentTest() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_document';
// Create 2 test files.
$test_filename = $this->randomMachineName() . '.txt';
$test_filepath = 'public://' . $test_filename;
$test_filename_updated = $this->randomMachineName() . '.txt';
$test_filepath_updated = 'public://' . $test_filename_updated;
file_put_contents($test_filepath, $this->randomMachineName());
file_put_contents($test_filepath_updated, $this->randomMachineName());
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/document');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("files[{$source_field_id}_0]");
// Create a media item.
$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');
$file_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$file_media_id = reset($file_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $file_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($file_media_id);
$this->assertSame($test_filename, $media->label());
// Here we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-document > *', 1);
// Assert the file link is present in the media element and its text matches
// the filename.
$link_element = $assert_session->elementExists('css', 'div.media--type-document .field--name-field-media-document a');
$this->assertSame($test_filename, $link_element->getText());
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $file_media_id . '/edit');
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath_updated));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($file_media_id);
$this->assertSame($test_filename_updated, $media->label());
// Again we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-document > *', 1);
// Assert the file link is present in the media element and its text matches
// the updated filename.
$link_element = $assert_session->elementExists('css', 'div.media--type-document .field--name-field-media-document a');
$this->assertSame($test_filename_updated, $link_element->getText());
}
/**
* Tests the standard profile configuration for media type 'remote_video'.
*/
protected function remoteVideoTest() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_oembed_video';
// Set video fixtures.
$video_title = 'Drupal Rap Video - Schipulcon09';
$video_url = 'https://vimeo.com/7073899';
ResourceController::setResourceUrl($video_url, $this->getFixturesDirectory() . '/video_vimeo.json');
$video_title_updated = "Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)";
$video_url_updated = 'https://www.youtube.com/watch?v=PWjcqE3QKBg';
ResourceController::setResourceUrl($video_url_updated, $this->getFixturesDirectory() . '/video_youtube.json');
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/remote_video');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("{$source_field_id}[0][value]");
// Create a media item.
$page->fillField("{$source_field_id}[0][value]", $video_url);
$page->pressButton('Save');
$remote_video_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$remote_video_media_id = reset($remote_video_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $remote_video_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($remote_video_media_id);
$this->assertSame($video_title, $media->label());
// Here we expect to see only the video iframe. Assert only one element in
// the content region.
$assert_session->elementsCount('css', 'div.media--type-remote-video > *', 1);
// Assert the iframe is present in the media element and its src attribute
// matches the URL and query parameters.
$iframe_url = $assert_session->elementExists('css', 'div.media--type-remote-video .field--name-field-media-oembed-video iframe')->getAttribute('src');
$iframe_url = parse_url($iframe_url);
$this->assertStringEndsWith('/media/oembed', $iframe_url['path']);
$this->assertNotEmpty($iframe_url['query']);
$query = [];
parse_str($iframe_url['query'], $query);
$this->assertSame($video_url, $query['url']);
$this->assertNotEmpty($query['hash']);
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $remote_video_media_id . '/edit');
$page->fillField("{$source_field_id}[0][value]", $video_url_updated);
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($remote_video_media_id);
$this->assertSame($video_title_updated, $media->label());
// Again we expect to see only the video iframe. Assert only one element in
// the content region.
$assert_session->elementsCount('css', 'div.media--type-remote-video > *', 1);
// Assert the iframe is present in the media element and its src attribute
// matches the updated URL and query parameters.
$iframe_url = $assert_session->elementExists('css', 'div.media--type-remote-video .field--name-field-media-oembed-video iframe')->getAttribute('src');
$iframe_url = parse_url($iframe_url);
$this->assertStringEndsWith('/media/oembed', $iframe_url['path']);
$this->assertNotEmpty($iframe_url['query']);
$query = [];
parse_str($iframe_url['query'], $query);
$this->assertSame($video_url_updated, $query['url']);
$this->assertNotEmpty($query['hash']);
}
/**
* Tests the standard profile configuration for media type 'video'.
*/
protected function videoTest() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_video_file';
// Create 2 test files.
$test_filename = $this->randomMachineName() . '.mp4';
$test_filepath = 'public://' . $test_filename;
$test_filename_updated = $this->randomMachineName() . '.mp4';
$test_filepath_updated = 'public://' . $test_filename_updated;
file_put_contents($test_filepath, str_repeat('t', 10));
file_put_contents($test_filepath_updated, str_repeat('u', 10));
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/video');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("files[{$source_field_id}_0]");
// Create a media item.
$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');
$video_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$video_media_id = reset($video_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $video_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($video_media_id);
$this->assertSame($test_filename, $media->label());
// Here we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-video > *', 1);
// Assert the video element is present inside the media element and that its
// src attribute matches the video file.
$video_element = $assert_session->elementExists('css', 'div.media--type-video .field--name-field-media-video-file video > source');
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_video_src = $file_url_generator->generateString(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename));
$this->assertSame($expected_video_src, $video_element->getAttribute('src'));
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $video_media_id . '/edit');
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath_updated));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($video_media_id);
$this->assertSame($test_filename_updated, $media->label());
// Again we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-video > *', 1);
// Assert the video element is present inside the media element and that its
// src attribute matches the updated video file.
$video_element = $assert_session->elementExists('css', 'div.media--type-video .field--name-field-media-video-file video > source');
$expected_video_src = $file_url_generator->generateString(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename_updated));
$this->assertSame($expected_video_src, $video_element->getAttribute('src'));
}
}

View File

@@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Component\Utility\Html;
// cspell:ignore pastafazoul
/**
* Tests the media type creation.
*
* @group media
*/
class MediaTypeCreationTest extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the source field behavior on the add media type form.
*/
public function testSourceChangeOnMediaTypeCreationForm(): void {
$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);
$this->assertNotEmpty(
$assert_session->waitForElementVisible('css', '.machine-name-value')
);
// Select the media source used by our media type.
$assert_session->selectExists('Media source')->selectOption('test_different_displays');
$this->assertNotEmpty(
$assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]')
);
// Change the media source.
$assert_session->selectExists('Media source')->selectOption('test');
$this->assertNotEmpty(
$assert_session->waitForElement('css', 'fieldset[data-drupal-selector="edit-source-configuration"] .fieldset-wrapper .placeholder:contains("Text (plain)")')
);
$page->pressButton('Save');
// Check that source can not be changed anymore.
$this->drupalGet("admin/structure/media/manage/{$mediaTypeMachineName}");
$assert_session->pageTextContains('The media source cannot be changed after the media type is created');
$assert_session->fieldDisabled('Media source');
}
/**
* Tests the media type creation form.
*/
public function testMediaTypeCreationFormWithDefaultField(): void {
$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');
// Select the media source used by our media type. Do this before setting
// the label or machine name in order to guard against the regression in
// https://www.drupal.org/project/drupal/issues/2557299.
$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);
// 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}'");
$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.');
// Check that the plugin cannot be changed after it is set on type creation.
$assert_session->fieldDisabled('Media source');
$assert_session->pageTextContains('The media source cannot be changed after the media type is created.');
// Check that the field map options are sorted alphabetically.
// Source field should not be included.
$options = $this->xpath('//select[@name="field_map[attribute_1]"]/option');
$this->assertGreaterThanOrEqual(2, count($options));
$this->assertSame('- Skip field -', $options[0]->getText());
$this->assertSame('Name', $options[1]->getText());
// It should not be possible to map the source field.
$assert_session->optionNotExists('field_map[attribute_1]', 'Test source');
// Open up the media add form and verify that the source field is right
// after the name, and before the vertical tabs.
$this->drupalGet("/media/add/$mediaTypeMachineName");
// Get the form element, and its HTML representation.
$form_selector = '#media-' . Html::cleanCssIdentifier($mediaTypeMachineName) . '-add-form';
$form = $assert_session->elementExists('css', $form_selector);
$form_html = $form->getOuterHtml();
// The name field should come before the source field, which should itself
// come before the vertical tabs.
$name_field = $assert_session->fieldExists('Name', $form)->getOuterHtml();
$test_source_field = $assert_session->fieldExists('Test source', $form)->getOuterHtml();
$vertical_tabs = $assert_session->elementExists('css', '.vertical-tabs', $form)->getOuterHtml();
$date_field = $assert_session->fieldExists('Date', $form)->getOuterHtml();
$published_checkbox = $assert_session->fieldExists('Published', $form)->getOuterHtml();
$this->assertGreaterThan(strpos($form_html, $name_field), strpos($form_html, $test_source_field));
$this->assertGreaterThan(strpos($form_html, $test_source_field), strpos($form_html, $vertical_tabs));
// The "Published" checkbox should be the last element.
$this->assertGreaterThan(strpos($form_html, $date_field), strpos($form_html, $published_checkbox));
// Check that a new type with the same machine name cannot be created.
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', $label);
$session->wait(5000, "jQuery('.machine-name-value').text() === '{$mediaTypeMachineName}'");
$page->selectFieldOption('Media source', 'test');
$assert_session->assertWaitOnAjaxRequest();
$page->pressButton('Save');
$assert_session->pageTextContains('The machine-readable name is already in use. It must be unique.');
}
/**
* Tests creation of media type, reusing an existing source field.
*/
public function testMediaTypeCreationReuseSourceField(): void {
$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');
// Choose the source plugin before setting the label and machine name.
$page->selectFieldOption('Media source', 'test');
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
$page->fillField('label', 'Pastafazoul');
$session->wait(5000, "jQuery('.machine-name-value').text() === 'pastafazoul'");
$page->pressButton('Save');
$label = 'Type reusing Default Field';
$mediaTypeMachineName = str_replace(' ', '_', strtolower($label));
$this->drupalGet('admin/structure/media/add');
// Select the media source used by our media type. Do this before setting
// the label and machine name.
$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');
// 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}'");
$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');
}
}

View File

@@ -0,0 +1,216 @@
<?php
declare(strict_types=1);
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
*/
protected static $modules = [
'block',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The test media type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testMediaType;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Tests a media type administration.
*/
public function testMediaTypes(): void {
$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 and manage fields');
// The wait prevents intermittent test failures.
$result = $assert_session->waitForLink('Create a new field');
$this->assertNotEmpty($result);
$assert_session->addressEquals('admin/structure/media/manage/' . $machine_name . '/fields');
$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);
$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');
// The wait prevents intermittent test failures.
$result = $assert_session->waitForLink('Add media type');
$this->assertNotEmpty($result);
$assert_session->addressEquals('admin/structure/media');
$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->assertSame($loaded_media_type->id(), $this->testMediaType->id());
$this->assertSame($loaded_media_type->label(), $new_name);
$this->assertSame($loaded_media_type->getDescription(), $new_description);
$this->assertSame($loaded_media_type->getSource()->getPluginId(), 'test');
$this->assertSame($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->assertSame($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->assertWaitOnAjaxRequest();
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-modal'));
$assert_session->addressEquals('admin/structure/media/manage/' . $this->testMediaType->id());
$this->click('.ui-dialog button:contains("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('test');
$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->assertWaitOnAjaxRequest();
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-modal'));
$assert_session->addressEquals('admin/structure/media/manage/' . $media_type2->id());
$assert_session->elementNotExists('css', '.ui-dialog button:contains("Delete")');
$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.");
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
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 {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests adding a view of media.
*/
public function testMediaWizard(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->createMediaType('test');
$view_id = $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->assertSame($session->getCurrentUrl(), $this->baseUrl . '/admin/structure/views/view/' . $view_id);
$view = Views::getView($view_id);
$view->initHandlers();
$row = $view->display_handler->getOption('row');
$this->assertSame($row['type'], 'fields');
// Check for the default filters.
$this->assertSame($view->filter['status']->table, 'media_field_data');
$this->assertSame($view->filter['status']->field, 'status');
$this->assertSame($view->filter['status']->value, '1');
// Check for the default fields.
$this->assertSame($view->field['name']->table, 'media_field_data');
$this->assertSame($view->field['name']->field, 'name');
}
/**
* Tests adding a view of media revisions.
*/
public function testMediaRevisionWizard(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$view_id = $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->assertSame($session->getCurrentUrl(), $this->baseUrl . '/admin/structure/views/view/' . $view_id);
$view = Views::getView($view_id);
$view->initHandlers();
$row = $view->display_handler->getOption('row');
$this->assertSame($row['type'], 'fields');
// Check for the default filters.
$this->assertSame($view->filter['status']->table, 'media_field_revision');
$this->assertSame($view->filter['status']->field, 'status');
$this->assertSame($view->filter['status']->value, '1');
// Check for the default fields.
$this->assertSame($view->field['name']->table, 'media_field_revision');
$this->assertSame($view->field['name']->field, 'name');
$this->assertSame($view->field['changed']->table, 'media_field_revision');
$this->assertSame($view->field['changed']->field, 'changed');
}
}

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