diff --git a/modules/gmedia/config/optional/views.view.group_media.yml b/modules/gmedia/config/optional/views.view.group_media.yml
new file mode 100644
index 0000000..0c4fd10
--- /dev/null
+++ b/modules/gmedia/config/optional/views.view.group_media.yml
@@ -0,0 +1,841 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - group
+ - media
+id: group_media
+label: 'Group media'
+module: views
+description: ''
+tag: ''
+base_table: media_field_data
+base_field: mid
+core: 8.x
+display:
+ default:
+ display_plugin: default
+ id: default
+ display_title: Master
+ position: 0
+ display_options:
+ access:
+ type: group_permission
+ options:
+ group_permission: 'access group_media overview'
+ cache:
+ type: tag
+ options: { }
+ query:
+ type: views_query
+ options:
+ disable_sql_rewrite: false
+ distinct: false
+ replica: false
+ query_comment: ''
+ query_tags: { }
+ exposed_form:
+ type: basic
+ options:
+ submit_button: 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: mini
+ options:
+ items_per_page: 50
+ offset: 0
+ id: 0
+ total_pages: null
+ expose:
+ items_per_page: false
+ items_per_page_label: 'Items per page'
+ items_per_page_options: '5, 10, 25, 50'
+ items_per_page_options_all: false
+ items_per_page_options_all_label: '- All -'
+ offset: false
+ offset_label: Offset
+ tags:
+ previous: ‹‹
+ next: ››
+ style:
+ type: table
+ row:
+ type: fields
+ fields:
+ name:
+ id: name
+ table: media_field_data
+ field: name
+ entity_type: media
+ entity_field: media
+ alter:
+ alter_text: false
+ make_link: false
+ absolute: false
+ trim: false
+ word_boundary: false
+ ellipsis: false
+ strip_tags: false
+ html: false
+ hide_empty: false
+ empty_zero: false
+ settings:
+ link_to_entity: true
+ plugin_id: field
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: Name
+ exclude: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_alter_empty: true
+ click_sort_column: value
+ type: string
+ group_column: value
+ group_columns: { }
+ group_rows: true
+ delta_limit: 0
+ delta_offset: 0
+ delta_reversed: false
+ delta_first_last: false
+ multi_type: separator
+ separator: ', '
+ field_api_classes: false
+ bundle:
+ id: bundle
+ table: media_field_data
+ field: bundle
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: 'Media type'
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ click_sort_column: target_id
+ type: entity_reference_label
+ settings:
+ link: false
+ group_column: target_id
+ group_columns: { }
+ group_rows: true
+ delta_limit: 0
+ delta_offset: 0
+ delta_reversed: false
+ delta_first_last: false
+ multi_type: separator
+ separator: ', '
+ field_api_classes: false
+ entity_type: media
+ entity_field: bundle
+ plugin_id: field
+ status:
+ id: status
+ table: media_field_data
+ field: status
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: Status
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ click_sort_column: value
+ type: boolean
+ settings:
+ format: custom
+ format_custom_true: Published
+ format_custom_false: Unpublished
+ group_column: value
+ group_columns: { }
+ group_rows: true
+ delta_limit: 0
+ delta_offset: 0
+ delta_reversed: false
+ delta_first_last: false
+ multi_type: separator
+ separator: ', '
+ field_api_classes: false
+ entity_type: media
+ entity_field: status
+ plugin_id: field
+ changed:
+ id: changed
+ table: media_field_data
+ field: changed
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: Updated
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ click_sort_column: value
+ type: timestamp
+ settings:
+ date_format: medium
+ custom_date_format: ''
+ timezone: ''
+ group_column: value
+ group_columns: { }
+ group_rows: true
+ delta_limit: 0
+ delta_offset: 0
+ delta_reversed: false
+ delta_first_last: false
+ multi_type: separator
+ separator: ', '
+ field_api_classes: false
+ entity_type: media
+ entity_field: changed
+ plugin_id: field
+ view_group_content:
+ id: view_group_content
+ table: group_content
+ field: view_group_content
+ relationship: group_content
+ group_type: group
+ admin_label: ''
+ label: 'Link to Group content'
+ exclude: true
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ text: 'View relation'
+ output_url_as_text: false
+ absolute: false
+ entity_type: group_content
+ plugin_id: entity_link
+ edit_group_content:
+ id: edit_group_content
+ table: group_content
+ field: edit_group_content
+ relationship: group_content
+ group_type: group
+ admin_label: ''
+ label: 'Link to edit Group content'
+ exclude: true
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ text: 'Edit relation'
+ output_url_as_text: false
+ absolute: false
+ entity_type: group_content
+ plugin_id: entity_link_edit
+ delete_group_content:
+ id: delete_group_content
+ table: group_content
+ field: delete_group_content
+ relationship: group_content
+ group_type: group
+ admin_label: ''
+ label: 'Link to delete Group content'
+ exclude: true
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ text: 'Delete relation'
+ output_url_as_text: false
+ absolute: false
+ entity_type: group_content
+ plugin_id: entity_link_delete
+ edit_media:
+ id: edit_media
+ table: media
+ field: edit_media
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: 'Link to edit Media'
+ exclude: true
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ text: 'Edit media'
+ output_url_as_text: false
+ absolute: false
+ entity_type: media
+ plugin_id: entity_link_edit
+ delete_media:
+ id: delete_media
+ table: media
+ field: delete_media
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: 'Link to delete Media'
+ exclude: true
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ text: 'Delete node'
+ output_url_as_text: false
+ absolute: false
+ entity_type: media
+ plugin_id: entity_link_delete
+ dropbutton:
+ id: dropbutton
+ table: views
+ field: dropbutton
+ relationship: none
+ group_type: group
+ admin_label: ''
+ label: Operations
+ exclude: false
+ alter:
+ alter_text: false
+ text: ''
+ make_link: false
+ path: ''
+ absolute: false
+ external: false
+ replace_spaces: false
+ path_case: none
+ trim_whitespace: false
+ alt: ''
+ rel: ''
+ link_class: ''
+ prefix: ''
+ suffix: ''
+ target: ''
+ nl2br: false
+ max_length: 0
+ word_boundary: true
+ ellipsis: true
+ more_link: false
+ more_link_text: ''
+ more_link_path: ''
+ strip_tags: false
+ trim: false
+ preserve_tags: ''
+ html: false
+ element_type: ''
+ element_class: ''
+ element_label_type: ''
+ element_label_class: ''
+ element_label_colon: true
+ element_wrapper_type: ''
+ element_wrapper_class: ''
+ element_default_classes: true
+ empty: ''
+ hide_empty: false
+ empty_zero: false
+ hide_alter_empty: true
+ fields:
+ view_group_content: view_group_content
+ edit_group_content: edit_group_content
+ delete_group_content: delete_group_content
+ edit_media: edit_media
+ delete_media: delete_media
+ name: '0'
+ bundle: '0'
+ status: '0'
+ changed: '0'
+ destination: true
+ plugin_id: dropbutton
+ filters:
+ status:
+ id: status
+ table: media_field_data
+ field: status
+ relationship: none
+ group_type: group
+ admin_label: ''
+ operator: '='
+ value: '1'
+ group: 1
+ exposed: true
+ expose:
+ operator_id: ''
+ label: Published
+ description: null
+ use_operator: false
+ operator: status_op
+ identifier: status
+ required: true
+ remember: false
+ multiple: false
+ remember_roles:
+ authenticated: authenticated
+ is_grouped: true
+ group_info:
+ label: 'Published status'
+ description: ''
+ identifier: status
+ optional: true
+ widget: select
+ multiple: false
+ remember: false
+ default_group: All
+ default_group_multiple: { }
+ group_items:
+ 1:
+ title: Published
+ operator: '='
+ value: '1'
+ 2:
+ title: Unpublished
+ operator: '='
+ value: '0'
+ plugin_id: boolean
+ entity_type: media
+ entity_field: status
+ bundle:
+ id: bundle
+ table: media_field_data
+ field: bundle
+ relationship: none
+ group_type: group
+ admin_label: ''
+ operator: in
+ value: { }
+ group: 1
+ exposed: true
+ expose:
+ operator_id: bundle_op
+ label: Type
+ description: ''
+ use_operator: false
+ operator: bundle_op
+ identifier: bundle
+ required: false
+ remember: false
+ multiple: false
+ remember_roles:
+ authenticated: authenticated
+ anonymous: '0'
+ administrator: '0'
+ reduce: false
+ is_grouped: false
+ group_info:
+ label: ''
+ description: ''
+ identifier: ''
+ optional: true
+ widget: select
+ multiple: false
+ remember: false
+ default_group: All
+ default_group_multiple: { }
+ group_items: { }
+ entity_type: media
+ entity_field: bundle
+ plugin_id: bundle
+ sorts: { }
+ title: 'Group media'
+ header: { }
+ footer: { }
+ empty:
+ area_text_custom:
+ id: area_text_custom
+ table: views
+ field: area_text_custom
+ relationship: none
+ group_type: group
+ admin_label: ''
+ empty: true
+ tokenize: false
+ content: 'No content available.'
+ plugin_id: text_custom
+ relationships:
+ group_content:
+ id: group_content
+ table: media_field_data
+ field: group_content
+ relationship: none
+ group_type: group
+ admin_label: 'Content relation'
+ required: true
+ group_content_plugins:
+ 'group_media:audio': '0'
+ 'group_media:file': '0'
+ 'group_media:image': '0'
+ 'group_media:remote_video': '0'
+ 'group_media:video': '0'
+ entity_type: media
+ plugin_id: group_content_to_entity_reverse
+ arguments:
+ gid:
+ id: gid
+ table: group_content_field_data
+ field: gid
+ relationship: group_content
+ group_type: group
+ admin_label: ''
+ default_action: 'access denied'
+ exception:
+ value: all
+ title_enable: false
+ title: All
+ title_enable: true
+ title: '{{ arguments.gid|placeholder }} media'
+ default_argument_type: fixed
+ default_argument_options:
+ argument: ''
+ default_argument_skip_url: false
+ summary_options:
+ base_path: ''
+ count: true
+ items_per_page: 25
+ override: false
+ summary:
+ sort_order: asc
+ number_of_records: 0
+ format: default_summary
+ specify_validation: true
+ validate:
+ type: 'entity:group'
+ fail: 'not found'
+ validate_options:
+ operation: view
+ multiple: 0
+ bundles: { }
+ access: false
+ break_phrase: false
+ not: false
+ entity_type: group_content
+ entity_field: gid
+ plugin_id: group_id
+ display_extenders: { }
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_content'
+ - 'languages:language_interface'
+ - route.group
+ - url
+ - url.query_args
+ - user.group_permissions
+ tags: { }
+ page_1:
+ display_plugin: page
+ id: page_1
+ display_title: Page
+ position: 1
+ display_options:
+ display_extenders: { }
+ path: group/%group/media
+ menu:
+ type: tab
+ title: Media
+ description: ''
+ expanded: false
+ parent: ''
+ weight: 30
+ context: '0'
+ menu_name: main
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - 'languages:language_content'
+ - 'languages:language_interface'
+ - route.group
+ - url
+ - url.query_args
+ - user.group_permissions
+ tags: { }
diff --git a/modules/gmedia/gmedia.group.permissions.yml b/modules/gmedia/gmedia.group.permissions.yml
new file mode 100644
index 0000000..dd6921f
--- /dev/null
+++ b/modules/gmedia/gmedia.group.permissions.yml
@@ -0,0 +1,3 @@
+access group_media overview:
+ title: 'Access group media overview'
+ description: 'Access the overview of all group media items, regardless of bundle'
diff --git a/modules/gmedia/gmedia.info.yml b/modules/gmedia/gmedia.info.yml
new file mode 100644
index 0000000..487cd50
--- /dev/null
+++ b/modules/gmedia/gmedia.info.yml
@@ -0,0 +1,9 @@
+name: 'Group Media'
+description: 'Enables Group functionality for media items'
+package: 'Group'
+type: 'module'
+version: 1.0
+core_version_requirement: ^8.8 || ^9
+dependencies:
+ - 'media'
+ - 'group'
diff --git a/modules/gmedia/gmedia.links.action.yml b/modules/gmedia/gmedia.links.action.yml
new file mode 100644
index 0000000..d200a22
--- /dev/null
+++ b/modules/gmedia/gmedia.links.action.yml
@@ -0,0 +1,11 @@
+group_content.group_media_add_page:
+ route_name: 'entity.group_content.group_media_add_page'
+ title: 'Add media item to group'
+ appears_on:
+ - 'view.group_media.page_1'
+
+group_content.group_media_relate_page:
+ route_name: 'entity.group_content.group_media_relate_page'
+ title: 'Relate media item to group'
+ appears_on:
+ - 'view.group_media.page_1'
diff --git a/modules/gmedia/gmedia.module b/modules/gmedia/gmedia.module
new file mode 100644
index 0000000..556a27a
--- /dev/null
+++ b/modules/gmedia/gmedia.module
@@ -0,0 +1,94 @@
+clearCachedDefinitions();
+}
+
+/**
+ * Implements hook_media_access().
+ *
+ * When trying to view, update or delete a media item it suffices to have the
+ * right to do so in only one group the media item belongs to. If you wish
+ * to prevent any such action on your own terms, implement hook_media_access()
+ * in your module.
+ */
+function gmedia_media_access(EntityInterface $media, $op, AccountInterface $account) {
+ // We do not care about create access as we have our own wizard for that. Any
+ // operation aside from 'view', 'update' and 'delete' is also unsupported.
+ if (!in_array($op, ['view', 'update', 'delete']) || !$media->id()) {
+ return AccessResult::neutral();
+ }
+
+ $plugin_id = 'group_media:' . $media->bundle();
+
+ // Only act if there are group content types for this media type.
+ $group_content_types = GroupContentType::loadByContentPluginId($plugin_id);
+ if (empty($group_content_types)) {
+ return AccessResult::neutral();
+ }
+
+ // Load all the group content for this media item.
+ $group_contents = \Drupal::entityTypeManager()
+ ->getStorage('group_content')
+ ->loadByProperties([
+ 'type' => array_keys($group_content_types),
+ 'entity_id' => $media->id(),
+ ]);
+
+ // If the media item does not belong to any group, we have nothing to say.
+ if (empty($group_contents)) {
+ return AccessResult::neutral();
+ }
+
+ /** @var \Drupal\group\Entity\GroupInterface[] $groups */
+ $groups = [];
+ foreach ($group_contents as $group_content) {
+ /** @var \Drupal\group\Entity\GroupContentInterface $group_content */
+ $group = $group_content->getGroup();
+ $groups[$group->id()] = $group;
+ }
+
+ // From this point on you need group to allow you to perform the requested
+ // operation. If you are not granted access for a media item belonging to
+ // a group, you should be denied access instead.
+ switch ($op) {
+ case 'view':
+ $is_published = $media->isPublished();
+
+ foreach ($groups as $group) {
+ if ((!$is_published && $group->hasPermission("view unpublished $plugin_id entity", $account)) ||
+ ($is_published && $group->hasPermission("view $plugin_id entity", $account))) {
+ return AccessResult::allowed();
+ }
+ }
+ break;
+
+ case 'update':
+ case 'delete':
+ foreach ($groups as $group) {
+ if ($group->hasPermission( "$op any $plugin_id entity", $account)) {
+ return AccessResult::allowed();
+ }
+ elseif ($account->id() == $media->getOwnerId() && $group->hasPermission("$op own $plugin_id entity", $account)) {
+ return AccessResult::allowed();
+ }
+ }
+ break;
+ }
+
+ return AccessResult::forbidden();
+}
diff --git a/modules/gmedia/gmedia.routing.yml b/modules/gmedia/gmedia.routing.yml
new file mode 100644
index 0000000..47f500e
--- /dev/null
+++ b/modules/gmedia/gmedia.routing.yml
@@ -0,0 +1,2 @@
+route_callbacks:
+ - '\Drupal\gmedia\Routing\GroupMediaRouteProvider::getRoutes'
diff --git a/modules/gmedia/gmedia.services.yml b/modules/gmedia/gmedia.services.yml
new file mode 100644
index 0000000..d839a91
--- /dev/null
+++ b/modules/gmedia/gmedia.services.yml
@@ -0,0 +1,5 @@
+services:
+ access_check.gmedia.admin:
+ class: Drupal\gmedia\Access\GroupMediaAdminAccessCheck
+ tags:
+ - { name: access_check, applies_to: _group_media_admin_access }
diff --git a/modules/gmedia/src/Access/GroupMediaAdminAccessCheck.php b/modules/gmedia/src/Access/GroupMediaAdminAccessCheck.php
new file mode 100644
index 0000000..fb55c32
--- /dev/null
+++ b/modules/gmedia/src/Access/GroupMediaAdminAccessCheck.php
@@ -0,0 +1,52 @@
+getRequirement('_group_media_admin_access') === 'TRUE';
+
+ // We can only get the group content type ID if the plugin is installed.
+ if (!$group->getGroupType()->hasContentPlugin("group_media:$media_type")) {
+ return AccessResult::neutral();
+ }
+
+ // Determine whether the user can manipulate items of the provided type.
+ $access = FALSE;
+ foreach (['create', 'edit', 'delete'] as $op) {
+ $permission = "$op $media_type item";
+ $access = $access || $group->hasPermission($permission, $account);
+ }
+
+ // Only allow access if the user can manipulate group items of the provided
+ // type or if he doesn't need access to do so.
+ return AccessResult::allowedIf($access xor !$needs_access);
+ }
+
+}
diff --git a/modules/gmedia/src/Controller/GroupMediaController.php b/modules/gmedia/src/Controller/GroupMediaController.php
new file mode 100644
index 0000000..537579a
--- /dev/null
+++ b/modules/gmedia/src/Controller/GroupMediaController.php
@@ -0,0 +1,114 @@
+pluginManager = $plugin_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('plugin.manager.group_content_enabler'),
+ $container->get('user.private_tempstore'),
+ $container->get('entity_type.manager'),
+ $container->get('entity.form_builder'),
+ $container->get('renderer')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function addPage(GroupInterface $group, $create_mode = FALSE) {
+ $build = parent::addPage($group, $create_mode);
+
+ // Do not interfere with redirects.
+ if (!is_array($build)) {
+ return $build;
+ }
+
+ // Overwrite the label and description for all of the displayed bundles.
+ $storage_handler = $this->entityTypeManager->getStorage('media_type');
+ foreach ($this->addPageBundles($group, $create_mode) as $plugin_id => $bundle_name) {
+ if (!empty($build['#bundles'][$bundle_name])) {
+ $plugin = $group->getGroupType()->getContentPlugin($plugin_id);
+ $bundle_label = $storage_handler->load($plugin->getEntityBundle())->label();
+
+ $t_args = ['%media_type' => $bundle_label];
+ $description = $create_mode
+ ? $this->t('Create a media of type %media_type in the group.', $t_args)
+ : $this->t('Add an existing media of type %media_type to the group.', $t_args);
+
+ $build['#bundles'][$bundle_name]['label'] = $bundle_label;
+ $build['#bundles'][$bundle_name]['description'] = $description;
+ }
+ }
+
+ return $build;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function addPageBundles(GroupInterface $group, $create_mode) {
+ $bundles = [];
+
+ // Retrieve all group_media plugins for the group's type.
+ $plugin_ids = $this->pluginManager->getInstalledIds($group->getGroupType());
+ foreach ($plugin_ids as $key => $plugin_id) {
+ if (strpos($plugin_id, 'group_media:') !== 0) {
+ unset($plugin_ids[$key]);
+ }
+ }
+
+ // Retrieve all of the responsible group content types, keyed by plugin ID.
+ $storage = $this->entityTypeManager->getStorage('group_content_type');
+ $properties = ['group_type' => $group->bundle(), 'content_plugin' => $plugin_ids];
+ foreach ($storage->loadByProperties($properties) as $bundle => $group_content_type) {
+ /** @var \Drupal\group\Entity\GroupContentTypeInterface $group_content_type */
+ $bundles[$group_content_type->getContentPluginId()] = $bundle;
+ }
+
+ return $bundles;
+ }
+
+}
diff --git a/modules/gmedia/src/Plugin/GroupConfigEnabler/GroupMediaType.php b/modules/gmedia/src/Plugin/GroupConfigEnabler/GroupMediaType.php
new file mode 100644
index 0000000..b34214a
--- /dev/null
+++ b/modules/gmedia/src/Plugin/GroupConfigEnabler/GroupMediaType.php
@@ -0,0 +1,18 @@
+getEntityBundle());
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getGroupOperations(GroupInterface $group) {
+ $account = \Drupal::currentUser();
+ $plugin_id = $this->getPluginId();
+ $type = $this->getEntityBundle();
+ $operations = [];
+
+ if ($group->hasPermission("create $plugin_id entity", $account)) {
+ $route_params = ['group' => $group->id(), 'plugin_id' => $plugin_id];
+ $operations["gmedia-create-$type"] = [
+ 'title' => $this->t('Create @type', ['@type' => $this->getMediaType()->label()]),
+ 'url' => new Url('entity.group_content.create_form', $route_params),
+ 'weight' => 30,
+ ];
+ }
+
+ return $operations;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getTargetEntityPermissions() {
+ $permissions = parent::getTargetEntityPermissions();
+ $plugin_id = $this->getPluginId();
+
+ // Add a 'view unpublished' permission by re-using most of the 'view' one.
+ $original = $permissions["view $plugin_id entity"];
+ $permissions["view unpublished $plugin_id entity"] = [
+ 'title' => str_replace('View ', 'View unpublished ', $original['title']),
+ ] + $original;
+
+ return $permissions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function defaultConfiguration() {
+ $config = parent::defaultConfiguration();
+ $config['entity_cardinality'] = 1;
+ return $config;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+ $form = parent::buildConfigurationForm($form, $form_state);
+
+ // Disable the entity cardinality field as the functionality of this module
+ // relies on a cardinality of 1. We don't just hide it, though, to keep a UI
+ // that's consistent with other content enabler plugins.
+ $info = $this->t("This field has been disabled by the plugin to guarantee the functionality that's expected of it.");
+ $form['entity_cardinality']['#disabled'] = TRUE;
+ $form['entity_cardinality']['#description'] .= '
' . $info . '';
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function calculateDependencies() {
+ $dependencies = parent::calculateDependencies();
+ $dependencies['config'][] = 'media.type.' . $this->getEntityBundle();
+ return $dependencies;
+ }
+
+}
diff --git a/modules/gmedia/src/Plugin/GroupContentEnabler/GroupMediaDeriver.php b/modules/gmedia/src/Plugin/GroupContentEnabler/GroupMediaDeriver.php
new file mode 100644
index 0000000..200e4a3
--- /dev/null
+++ b/modules/gmedia/src/Plugin/GroupContentEnabler/GroupMediaDeriver.php
@@ -0,0 +1,27 @@
+ $media_type) {
+ $label = $media_type->label();
+
+ $this->derivatives[$name] = [
+ 'entity_bundle' => $name,
+ 'label' => t('Group media (@type)', ['@type' => $label]),
+ 'description' => t('Adds %type content to groups both publicly and privately.', ['%type' => $label]),
+ ] + $base_plugin_definition;
+ }
+
+ return $this->derivatives;
+ }
+
+}
diff --git a/modules/gmedia/src/Routing/GroupMediaRouteProvider.php b/modules/gmedia/src/Routing/GroupMediaRouteProvider.php
new file mode 100644
index 0000000..b552e5a
--- /dev/null
+++ b/modules/gmedia/src/Routing/GroupMediaRouteProvider.php
@@ -0,0 +1,57 @@
+ $media_type) {
+ $plugin_id = "group_media:$name";
+
+ $plugin_ids[] = $plugin_id;
+ $permissions_add[] = "create $plugin_id content";
+ $permissions_create[] = "create $plugin_id entity";
+ }
+
+ // If there are no media types yet, we cannot have any plugin IDs and should
+ // therefore exit early because we cannot have any routes for them either.
+ if (empty($plugin_ids)) {
+ return $routes;
+ }
+
+ $routes['entity.group_content.group_media_relate_page'] = new Route('group/{group}/media/add');
+ $routes['entity.group_content.group_media_relate_page']
+ ->setDefaults([
+ '_title' => 'Relate media',
+ '_controller' => '\Drupal\gmedia\Controller\GroupMediaController::addPage',
+ ])
+ ->setRequirement('_group_permission', implode('+', $permissions_add))
+ ->setRequirement('_group_installed_content', implode('+', $plugin_ids))
+ ->setOption('_group_operation_route', TRUE);
+
+ $routes['entity.group_content.group_media_add_page'] = new Route('group/{group}/media/create');
+ $routes['entity.group_content.group_media_add_page']
+ ->setDefaults([
+ '_title' => 'Create media',
+ '_controller' => '\Drupal\gmedia\Controller\GroupMediaController::addPage',
+ 'create_mode' => TRUE,
+ ])
+ ->setRequirement('_group_permission', implode('+', $permissions_create))
+ ->setRequirement('_group_installed_content', implode('+', $plugin_ids))
+ ->setOption('_group_operation_route', TRUE);
+
+ return $routes;
+ }
+
+}