Skip to content

Commit a6e4eaf

Browse files
committed
Issue #3017935 by chr.fritsch, Wim Leers, alexpott, phenaproxima: Media items should not be available at /media/{id} to users with 'view media' permission by default
1 parent ee8ab0b commit a6e4eaf

22 files changed

+572
-77
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
icon_base_uri: 'public://media-icons/generic'
22
iframe_domain: ''
33
oembed_providers_url: 'https://oembed.com/providers.json'
4+
standalone_url: false

core/modules/media/config/schema/media.schema.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ media.settings:
1111
oembed_providers_url:
1212
type: uri
1313
label: 'The URL of the oEmbed providers database in JSON format'
14+
standalone_url:
15+
type: boolean
16+
label: 'Allow media items to be viewed standalone at /media/{id}'
1417

1518
media.type.*:
1619
type: config_entity

core/modules/media/media.links.task.yml

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,5 @@
1-
entity.media.canonical:
2-
title: View
3-
route_name: entity.media.canonical
4-
base_route: entity.media.canonical
5-
6-
entity.media.edit_form:
7-
title: Edit
8-
route_name: entity.media.edit_form
9-
base_route: entity.media.canonical
10-
11-
entity.media.delete_form:
12-
title: Delete
13-
route_name: entity.media.delete_form
14-
base_route: entity.media.canonical
15-
weight: 10
1+
media.tasks:
2+
deriver: 'Drupal\media\Plugin\Derivative\DynamicLocalTasks'
163

174
entity.media_type.edit_form:
185
title: Edit

core/modules/media/media.module

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,3 +347,14 @@ function _media_get_add_url($allowed_bundles) {
347347

348348
return FALSE;
349349
}
350+
351+
/**
352+
* Implements hook_entity_type_alter().
353+
*/
354+
function media_entity_type_alter(array &$entity_types) {
355+
if (\Drupal::config('media.settings')->get('standalone_url')) {
356+
/** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
357+
$entity_type = $entity_types['media'];
358+
$entity_type->setLinkTemplate('canonical', '/media/{media}');
359+
}
360+
}

core/modules/media/media.post_update.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@ function media_post_update_collection_route() {
1818
function media_post_update_storage_handler() {
1919
// Empty post-update hook.
2020
}
21+
22+
/**
23+
* Keep media items viewable at /media/{id}.
24+
*/
25+
function media_post_update_enable_standalone_url() {
26+
\Drupal::configFactory()
27+
->getEditable('media.settings')
28+
->set('standalone_url', TRUE)
29+
->save(TRUE);
30+
}

core/modules/media/media.services.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ services:
1919
media.oembed.iframe_url_helper:
2020
class: Drupal\media\IFrameUrlHelper
2121
arguments: ['@router.request_context', '@private_key']
22+
media.config_subscriber:
23+
class: Drupal\media\EventSubscriber\MediaConfigSubscriber
24+
arguments: ['@router.builder', '@cache_tags.invalidator', '@entity_type.manager']
25+
tags:
26+
- { name: event_subscriber }

core/modules/media/src/Entity/Media.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
* "translation" = "Drupal\content_translation\ContentTranslationHandler",
4444
* "views_data" = "Drupal\media\MediaViewsData",
4545
* "route_provider" = {
46-
* "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
46+
* "html" = "Drupal\media\Routing\MediaRouteProvider",
4747
* }
4848
* },
4949
* base_table = "media",
@@ -75,7 +75,7 @@
7575
* links = {
7676
* "add-page" = "/media/add",
7777
* "add-form" = "/media/add/{media_type}",
78-
* "canonical" = "/media/{media}",
78+
* "canonical" = "/media/{media}/edit",
7979
* "collection" = "/admin/content/media",
8080
* "delete-form" = "/media/{media}/delete",
8181
* "delete-multiple-form" = "/media/delete",
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
namespace Drupal\media\EventSubscriber;
4+
5+
use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
6+
use Drupal\Core\Config\ConfigCrudEvent;
7+
use Drupal\Core\Config\ConfigEvents;
8+
use Drupal\Core\Entity\EntityTypeManagerInterface;
9+
use Drupal\Core\Routing\RouteBuilderInterface;
10+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
11+
12+
/**
13+
* Listens to the config save event for media.settings.
14+
*/
15+
class MediaConfigSubscriber implements EventSubscriberInterface {
16+
17+
/**
18+
* The route builder.
19+
*
20+
* @var \Drupal\Core\Routing\RouteBuilderInterface
21+
*/
22+
protected $routeBuilder;
23+
24+
/**
25+
* The cache tags invalidator.
26+
*
27+
* @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface
28+
*/
29+
protected $cacheTagsInvalidator;
30+
31+
/**
32+
* The entity type manager.
33+
*
34+
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
35+
*/
36+
protected $entityTypeManager;
37+
38+
/**
39+
* Constructs the MediaConfigSubscriber.
40+
*
41+
* @param \Drupal\Core\Routing\RouteBuilderInterface $router_builder
42+
* The route builder.
43+
* @param \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cache_tags_invalidator
44+
* The cache tags invalidator.
45+
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
46+
* The entity type manager.
47+
*/
48+
public function __construct(RouteBuilderInterface $router_builder, CacheTagsInvalidatorInterface $cache_tags_invalidator, EntityTypeManagerInterface $entity_type_manager) {
49+
$this->routeBuilder = $router_builder;
50+
$this->cacheTagsInvalidator = $cache_tags_invalidator;
51+
$this->entityTypeManager = $entity_type_manager;
52+
}
53+
54+
/**
55+
* Updates entity type definitions and ensures routes are rebuilt when needed.
56+
*
57+
* @param \Drupal\Core\Config\ConfigCrudEvent $event
58+
* The ConfigCrudEvent to process.
59+
*/
60+
public function onSave(ConfigCrudEvent $event) {
61+
$saved_config = $event->getConfig();
62+
if ($saved_config->getName() === 'media.settings' && $event->isChanged('standalone_url')) {
63+
$this->cacheTagsInvalidator->invalidateTags([
64+
// The configuration change triggers entity type definition changes,
65+
// which in turn triggers routes to appear or disappear.
66+
// @see media_entity_type_alter()
67+
'entity_types',
68+
// The 'rendered' cache tag needs to be explicitly invalidated to ensure
69+
// that all links to Media entities are re-rendered. Ideally, this would
70+
// not be necessary; invalidating the 'entity_types' cache tag should be
71+
// sufficient. But that cache tag would then need to be on nearly
72+
// everything, resulting in excessive complexity. We prefer pragmatism.
73+
'rendered',
74+
]);
75+
// @todo Remove this when invalidating the 'entity_types' cache tag is
76+
// respected by the entity type plugin manager. See
77+
// https://www.drupal.org/project/drupal/issues/3001284 and
78+
// https://www.drupal.org/project/drupal/issues/3013659.
79+
$this->entityTypeManager->clearCachedDefinitions();
80+
$this->routeBuilder->setRebuildNeeded();
81+
}
82+
}
83+
84+
/**
85+
* {@inheritdoc}
86+
*/
87+
public static function getSubscribedEvents() {
88+
$events[ConfigEvents::SAVE][] = ['onSave'];
89+
return $events;
90+
}
91+
92+
}

core/modules/media/src/Form/MediaSettingsForm.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Drupal\media\Form;
44

55
use Drupal\Core\Config\ConfigFactoryInterface;
6+
use Drupal\Core\Entity\EntityTypeManagerInterface;
67
use Drupal\Core\Form\FormStateInterface;
78
use Drupal\Core\Form\ConfigFormBase;
89
use Drupal\media\IFrameUrlHelper;
@@ -22,17 +23,27 @@ class MediaSettingsForm extends ConfigFormBase {
2223
*/
2324
protected $iFrameUrlHelper;
2425

26+
/**
27+
* The entity type manager.
28+
*
29+
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
30+
*/
31+
protected $entityTypeManager;
32+
2533
/**
2634
* MediaSettingsForm constructor.
2735
*
2836
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
2937
* The config factory service.
3038
* @param \Drupal\media\IFrameUrlHelper $iframe_url_helper
3139
* The iFrame URL helper service.
40+
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
41+
* The entity type manager.
3242
*/
33-
public function __construct(ConfigFactoryInterface $config_factory, IFrameUrlHelper $iframe_url_helper) {
43+
public function __construct(ConfigFactoryInterface $config_factory, IFrameUrlHelper $iframe_url_helper, EntityTypeManagerInterface $entity_type_manager) {
3444
parent::__construct($config_factory);
3545
$this->iFrameUrlHelper = $iframe_url_helper;
46+
$this->entityTypeManager = $entity_type_manager;
3647
}
3748

3849
/**
@@ -41,7 +52,8 @@ public function __construct(ConfigFactoryInterface $config_factory, IFrameUrlHel
4152
public static function create(ContainerInterface $container) {
4253
return new static(
4354
$container->get('config.factory'),
44-
$container->get('media.oembed.iframe_url_helper')
55+
$container->get('media.oembed.iframe_url_helper'),
56+
$container->get('entity_type.manager')
4557
);
4658
}
4759

@@ -91,6 +103,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
91103
'#description' => $this->t('Enter a different domain from which to serve oEmbed content, including the <em>http://</em> or <em>https://</em> prefix. This domain needs to point back to this site, or existing oEmbed content may not display correctly, or at all.'),
92104
];
93105

106+
$form['security']['standalone_url'] = [
107+
'#prefix' => '<hr>',
108+
'#type' => 'checkbox',
109+
'#title' => $this->t('Standalone media URL'),
110+
'#default_value' => $this->config('media.settings')->get('standalone_url'),
111+
'#description' => $this->t("Allow users to access @media-entities at /media/{id}.", ['@media-entities' => $this->entityTypeManager->getDefinition('media')->getPluralLabel()]),
112+
];
94113
return parent::buildForm($form, $form_state);
95114
}
96115

@@ -100,6 +119,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
100119
public function submitForm(array &$form, FormStateInterface $form_state) {
101120
$this->config('media.settings')
102121
->set('iframe_domain', $form_state->getValue('iframe_domain'))
122+
->set('standalone_url', $form_state->getValue('standalone_url'))
103123
->save();
104124

105125
parent::submitForm($form, $form_state);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
namespace Drupal\media\Plugin\Derivative;
4+
5+
use Drupal\Component\Plugin\Derivative\DeriverBase;
6+
use Drupal\Core\Config\ConfigFactoryInterface;
7+
use Drupal\Core\Entity\EntityTypeManagerInterface;
8+
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
9+
use Drupal\Core\StringTranslation\StringTranslationTrait;
10+
use Drupal\Core\StringTranslation\TranslationInterface;
11+
use Symfony\Component\DependencyInjection\ContainerInterface;
12+
13+
/**
14+
* Generates media-related local tasks.
15+
*/
16+
class DynamicLocalTasks extends DeriverBase implements ContainerDeriverInterface {
17+
18+
use StringTranslationTrait;
19+
20+
/**
21+
* The entity type manager.
22+
*
23+
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
24+
*/
25+
protected $entityTypeManager;
26+
27+
/**
28+
* The media settings config.
29+
*
30+
* @var \Drupal\Core\Config\ImmutableConfig
31+
*/
32+
protected $config;
33+
34+
/**
35+
* Creates a DynamicLocalTasks object.
36+
*
37+
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
38+
* The translation manager.
39+
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
40+
* The entity type manager.
41+
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
42+
* The config factory.
43+
*/
44+
public function __construct(TranslationInterface $string_translation, EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory) {
45+
$this->stringTranslation = $string_translation;
46+
$this->entityTypeManager = $entity_type_manager;
47+
$this->config = $config_factory->get('media.settings');
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public static function create(ContainerInterface $container, $base_plugin_id) {
54+
return new static(
55+
$container->get('string_translation'),
56+
$container->get('entity_type.manager'),
57+
$container->get('config.factory')
58+
);
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function getDerivativeDefinitions($base_plugin_definition) {
65+
// Provide an edit_form task if standalone media URLs are enabled.
66+
$this->derivatives["entity.media.canonical"] = [
67+
'route_name' => "entity.media.canonical",
68+
'title' => $this->t('Edit'),
69+
'base_route' => "entity.media.canonical",
70+
'weight' => 1,
71+
] + $base_plugin_definition;
72+
73+
if ($this->config->get('standalone_url')) {
74+
$this->derivatives["entity.media.canonical"]['title'] = $this->t('View');
75+
76+
$this->derivatives["entity.media.edit_form"] = [
77+
'route_name' => "entity.media.edit_form",
78+
'title' => $this->t('Edit'),
79+
'base_route' => 'entity.media.canonical',
80+
'weight' => 2,
81+
] + $base_plugin_definition;
82+
}
83+
84+
$this->derivatives["entity.media.delete_form"] = [
85+
'route_name' => "entity.media.delete_form",
86+
'title' => $this->t('Delete'),
87+
'base_route' => "entity.media.canonical",
88+
'weight' => 10,
89+
] + $base_plugin_definition;
90+
91+
return $this->derivatives;
92+
}
93+
94+
}

0 commit comments

Comments
 (0)