diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 9b9b992d..5fc1d6f6 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -53,7 +53,8 @@ jobs:
- name: Composer install
run: |
- docker-compose exec -T web composer install --no-interaction
+ docker-compose exec -T web composer config --merge --json "extra.patches" '{"drupal/group": {"Using a translatable string as a category for field type is deprecated - https://www.drupal.org/project/group/issues/3458530": "https://www.drupal.org/files/issues/2024-07-02/group-translate-string-as-category-is-deprecated_0.patch"}}'
+ docker-compose exec -T web composer require --dev cweagans/composer-patches
- name: PhpCS
run: |
@@ -66,3 +67,8 @@ jobs:
- name: PhpUnit
run: |
docker-compose exec -T web ./vendor/bin/phpunit -vvv --debug
+
+ - name: PhpUnit - groupmedia 3
+ run: |
+ docker-compose exec -T web composer require --dev drupal/groupmedia:^3 -W
+ docker-compose exec -T web ./vendor/bin/phpunit -vvv --debug --testsuite "Collabora Online Group"
diff --git a/README.md b/README.md
index 83c72505..8043f684 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,7 @@ Minimal steps to see the editor in action:
Advanced usage:
- Configure roles and permissions as in "User permissions" section below.
-- Create a non-admin user with sufficient roles, login,
+- Create a non-admin user with sufficient roles, login,
### Running the tests
@@ -198,19 +198,19 @@ not needed, if the Collabora instance is configured from outside of
Drupal.
For each media type, the module introduces four permissions:
-- "(media type): Edit any media file in Collabora"
+- "(media type): Edit any media file in Collabora"
Users with this permission are allowed to edit documents attached
to a media entity of the given type, using the Collabora Online
editor.
-- "(media type): Edit own media file in Collabora"
+- "(media type): Edit own media file in Collabora"
Users with this permission are allowed to edit documents attached
to a media entity of the given type, using the Collabora Online
editor, if they are the owner/author of that media entity.
-- "(media type): Preview published media file in Collabora"
+- "(media type): Preview published media file in Collabora"
Users with this permission are allowed to preview documents attached
to a published media entity of the given type, using the Collabora
Online editor in preview/readonly mode.
-- "(media type): Preview own unpublished media file in Collabora"
+- "(media type): Preview own unpublished media file in Collabora"
Users with this permission are allowed to preview documents attached
to an unpublished media entity of the given type, using the Collabora Online
editor in preview/readonly mode.
@@ -223,6 +223,14 @@ Developers can use entity access hooks to alter which users may edit
or preview media files in Collabora. This would allow to grant access
based on e.g. membership in a group.
+### Views
+
+The module integrates with Views by providing links as view fields, allowing
+users to perform specific operations on documents directly from the view display.
+
+These operations include actions such as "preview" and "edit," which can be
+easily accessed through the generated links.
+
### Other configuration
If you need to change the accepted extensions to upload, go to
@@ -246,6 +254,13 @@ upload_max_filesize = 30M
These set the limits to a maximum of 30M. You can change as appropriate.
+Sub-modules
+-------------
+
+### Collabora Online Group
+
+Integration of Collabora Online with Group module. Check out the [README](/modules/collabora_online_group/README.md) of the module.
+
License
-------
diff --git a/collabora_online.info.yml b/collabora_online.info.yml
index 209be32f..1230bfe7 100644
--- a/collabora_online.info.yml
+++ b/collabora_online.info.yml
@@ -1,6 +1,6 @@
name: Collabora Online
description: Integrate Collabora Online for document viewing and editing.
-package: Custom
+package: Collabora Online
configure: collabora-online.settings
dependencies:
- drupal:media
diff --git a/collabora_online.views.inc b/collabora_online.views.inc
new file mode 100644
index 00000000..275511b5
--- /dev/null
+++ b/collabora_online.views.inc
@@ -0,0 +1,28 @@
+ t('Link to view in Collabora Online'),
+ 'group' => t('Media'),
+ 'field' => [
+ 'id' => 'media_collabora_preview',
+ ],
+ ];
+ $data['media']['collabora_edit'] = [
+ 'title' => t('Link to edit in Collabora Online'),
+ 'group' => t('Media'),
+ 'field' => [
+ 'id' => 'media_collabora_edit',
+ ],
+ ];
+}
diff --git a/composer.json b/composer.json
index de768864..256f18f1 100644
--- a/composer.json
+++ b/composer.json
@@ -19,6 +19,7 @@
"drupal/core-composer-scaffold": "^10.3.6",
"drupal/core-dev": "^10.3.6",
"drupal/core-recommended": "^10.3.6",
+ "drupal/groupmedia": "^3 || ^4",
"drush/drush": "^12.4",
"openeuropa/task-runner": "^2@dev",
"openeuropa/task-runner-drupal-project-symlink": "^1.0.0-beta6",
diff --git a/config/schema/collabora_online.schema.yml b/config/schema/collabora_online.schema.yml
index 963dd305..9b2093ea 100644
--- a/config/schema/collabora_online.schema.yml
+++ b/config/schema/collabora_online.schema.yml
@@ -24,3 +24,15 @@ collabora_online.settings:
allowfullscreen:
type: boolean
label: 'Allow full-screen.'
+
+views.field.media_collabora_preview:
+ type: views_field
+ label: 'Collabora view link'
+ mapping:
+ text:
+ type: label
+ label: 'Text to display'
+
+views.field.media_collabora_edit:
+ type: views.field.media_collabora_preview
+ label: 'Collabora edit link'
diff --git a/modules/collabora_online_group/README.md b/modules/collabora_online_group/README.md
new file mode 100644
index 00000000..a3a9c62d
--- /dev/null
+++ b/modules/collabora_online_group/README.md
@@ -0,0 +1,35 @@
+Collabora Online Group
+=====================================
+
+This submodule integrates the Group module (https://www.drupal.org/project/group)
+by managing group-related permissions. It allows fine-grained control over user
+access within groups, enabling specific permissions for Collabora content and
+actions based on group membership.
+
+### Requirements
+
+- Groupmedia module: https://www.drupal.org/project/groupmedia
+ - Compatible with versions 3.x and 4.x.
+
+### User permissions
+
+The module maps existing Collabora media operation permissions to the group type
+instances.
+
+#### Permissions:
+- "(media type): Edit any media file in Collabora"
+- "(media type): Edit own media file in Collabora"
+- "(media type): Preview published media file in Collabora"
+- "(media type): Preview own unpublished media file in Collabora"
+
+Check [Collabora Online README](/README.md#user-permissions) for more information about permissions.
+
+### Views
+
+Additionally, the submodule modifies Groupmedia view configuration to add links
+for Collabora operations.
+
+License
+-------
+
+This module is published under the MPL-2.0 license.
diff --git a/modules/collabora_online_group/collabora_online_group.info.yml b/modules/collabora_online_group/collabora_online_group.info.yml
new file mode 100644
index 00000000..229a0249
--- /dev/null
+++ b/modules/collabora_online_group/collabora_online_group.info.yml
@@ -0,0 +1,9 @@
+name: Collabora Online Group
+description: Integrates Collabora Online with Group module
+package: Collabora Online
+dependencies:
+ - collabora_online:collabora_online
+ - groupmedia:groupmedia
+
+type: module
+core_version_requirement: ^10
diff --git a/modules/collabora_online_group/collabora_online_group.install b/modules/collabora_online_group/collabora_online_group.install
new file mode 100644
index 00000000..ec4f7b6f
--- /dev/null
+++ b/modules/collabora_online_group/collabora_online_group.install
@@ -0,0 +1,62 @@
+moduleExists('views') ||
+ !($view = View::load('group_media'))
+ ) {
+ return;
+ }
+
+ // Load display and apply changes.
+ $display = &$view->getDisplay('default');
+ if ($display === NULL) {
+ return;
+ }
+
+ // Add new fields to the display.
+ $display['display_options']['fields'] += [
+ 'collabora_preview' => [
+ 'id' => 'collabora_preview',
+ 'table' => 'media',
+ 'field' => 'collabora_preview',
+ 'plugin_id' => 'media_collabora_preview',
+ 'label' => '',
+ 'exclude' => TRUE,
+ 'text' => t('View in Collabora Online'),
+ ],
+ ];
+ $display['display_options']['fields'] += [
+ 'collabora_edit' => [
+ 'id' => 'collabora_edit',
+ 'table' => 'media',
+ 'field' => 'collabora_edit',
+ 'plugin_id' => 'media_collabora_edit',
+ 'label' => '',
+ 'exclude' => TRUE,
+ 'text' => t('Edit in Collabora Online'),
+ ],
+ ];
+ // Add new fields as options for the dropbutton, and move the dropbutton to
+ // the end of the array.
+ $dropbutton = $display['display_options']['fields']['dropbutton'];
+ $dropbutton['fields'] += [
+ 'collabora_preview' => 'collabora_preview',
+ 'collabora_edit' => 'collabora_edit',
+ ];
+ unset($display['display_options']['fields']['dropbutton']);
+ $display['display_options']['fields']['dropbutton'] = $dropbutton;
+
+ $view->save();
+}
diff --git a/modules/collabora_online_group/collabora_online_group.services.yml b/modules/collabora_online_group/collabora_online_group.services.yml
new file mode 100644
index 00000000..25f25af2
--- /dev/null
+++ b/modules/collabora_online_group/collabora_online_group.services.yml
@@ -0,0 +1,9 @@
+services:
+ group.relation_handler.permission_provider.collabora_group_media:
+ class: 'Drupal\collabora_online_group\Plugin\Group\RelationHandler\CollaboraPermissionProvider'
+ decorates: group.relation_handler.permission_provider.group_media
+ arguments: ["@group.relation_handler.permission_provider.collabora_group_media.inner"]
+
+ group.relation_handler.access_control.group_media:
+ class: 'Drupal\collabora_online_group\Plugin\Group\RelationHandler\CollaboraAccessControl'
+ arguments: ["@group.relation_handler.access_control"]
diff --git a/modules/collabora_online_group/src/Plugin/Group/RelationHandler/CollaboraAccessControl.php b/modules/collabora_online_group/src/Plugin/Group/RelationHandler/CollaboraAccessControl.php
new file mode 100644
index 00000000..3694007f
--- /dev/null
+++ b/modules/collabora_online_group/src/Plugin/Group/RelationHandler/CollaboraAccessControl.php
@@ -0,0 +1,45 @@
+parent = $parent;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function entityAccess(EntityInterface $entity, $operation, AccountInterface $account, $return_as_object = FALSE): AccessResultInterface|bool {
+ // Add support for unpublished operation: preview in collabora.
+ $check_published = $operation === 'preview in collabora' && $this->implementsPublishedInterface;
+
+ if ($check_published && !$entity->isPublished()) {
+ $operation .= ' unpublished';
+ }
+
+ return $this->parent->entityAccess($entity, $operation, $account, $return_as_object);
+ }
+
+}
diff --git a/modules/collabora_online_group/src/Plugin/Group/RelationHandler/CollaboraPermissionProvider.php b/modules/collabora_online_group/src/Plugin/Group/RelationHandler/CollaboraPermissionProvider.php
new file mode 100644
index 00000000..8d754ba6
--- /dev/null
+++ b/modules/collabora_online_group/src/Plugin/Group/RelationHandler/CollaboraPermissionProvider.php
@@ -0,0 +1,73 @@
+parent->buildPermissions();
+
+ /* @see \Drupal\group\Plugin\Group\RelationHandlerDefault\PermissionProvider::buildPermissions() */
+ $provider_chain = $this->groupRelationTypeManager()->getPermissionProvider($this->pluginId);
+
+ // Add Collabora permissions.
+ $prefix = 'Entity:';
+ if ($name = $provider_chain->getPermission('preview in collabora', 'entity')) {
+ $permissions[$name] = $this->buildPermission("$prefix Preview published %entity_type in collabora");
+ }
+ if ($name = $provider_chain->getPermission('preview in collabora unpublished', 'entity', 'own')) {
+ $permissions[$name] = $this->buildPermission("$prefix Preview own unpublished %entity_type in collabora");
+ }
+ if ($name = $provider_chain->getPermission('edit in collabora', 'entity')) {
+ $permissions[$name] = $this->buildPermission("$prefix Edit any %entity_type in collabora");
+ }
+ if ($name = $provider_chain->getPermission('edit in collabora', 'entity', 'own')) {
+ $permissions[$name] = $this->buildPermission("$prefix Edit own %entity_type in collabora");
+ }
+
+ return $permissions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPermission($operation, $target, $scope = 'any'): bool|string {
+ if (
+ $target === 'entity' &&
+ $this->definesEntityPermissions &&
+ ($this->implementsOwnerInterface || $scope === 'any')
+ ) {
+ switch ($operation) {
+ case 'preview in collabora':
+ if ($scope === 'any') {
+ return "preview $this->pluginId in collabora";
+ }
+
+ return FALSE;
+
+ case 'preview in collabora unpublished':
+ if ($this->implementsPublishedInterface && $scope === 'own') {
+ return "preview $scope unpublished $this->pluginId in collabora";
+ }
+
+ return FALSE;
+
+ case 'edit in collabora':
+ return "edit $scope $this->pluginId in collabora";
+ }
+ }
+
+ return $this->parent->getPermission($operation, $target, $scope);
+ }
+
+}
diff --git a/modules/collabora_online_group/tests/src/Functional/GroupMediaViewsTest.php b/modules/collabora_online_group/tests/src/Functional/GroupMediaViewsTest.php
new file mode 100644
index 00000000..a95267d3
--- /dev/null
+++ b/modules/collabora_online_group/tests/src/Functional/GroupMediaViewsTest.php
@@ -0,0 +1,146 @@
+createGroupType(['id' => 'group_type_1']);
+ $this->createMediaType('file', [
+ 'id' => 'document',
+ 'label' => 'Document',
+ ]);
+ $this->createGroupRole([
+ 'group_type' => 'group_type_1',
+ 'scope' => PermissionScopeInterface::INSIDER_ID,
+ 'global_role' => RoleInterface::AUTHENTICATED_ID,
+ 'permissions' => [
+ 'view group',
+ 'access group_media overview',
+ 'view group_media:document entity',
+ 'edit any group_media:document in collabora',
+ 'preview group_media:document in collabora',
+ ],
+ ]);
+ $this->createPluginRelation($group_type, 'group_media:document', [
+ 'group_cardinality' => 0,
+ 'entity_cardinality' => 1,
+ 'use_creation_wizard' => FALSE,
+ ]);
+
+ // Create content.
+ $group = $this->createGroup(['type' => 'group_type_1']);
+ for ($i = 0; $i < 3; $i++) {
+ $media = $this->createMediaEntity('document', [
+ 'id' => 'media_' . $i,
+ 'name' => 'Media ' . $i,
+ ]);
+ $group->addRelationship($media, 'group_media:document');
+ }
+ $user = $this->createUser([
+ 'view the administration theme',
+ 'access administration pages',
+ 'access group overview',
+ ]);
+ $group->addMember($user);
+
+ // Go to the page and check the links added to the view.
+ $this->drupalLogin($user);
+ $this->drupalGet("group/{$group->id()}/media");
+ $assert_session = $this->assertSession();
+
+ // Check table header.
+ $table = $assert_session->elementExists('css', 'table');
+ $table_header = $assert_session->elementExists('css', 'thead', $table);
+ $rows = $table_header->findAll('css', 'tr');
+ $cols = $rows[0]->findAll('css', 'th');
+ $this->assertEquals('Media name', $cols[0]->getText());
+ $this->assertEquals('Bundle', $cols[1]->getText());
+ $this->assertEquals('Status', $cols[2]->getText());
+ $this->assertEquals('Publisher', $cols[3]->getText());
+ // Support different versions of groupmedia.
+ $this->assertTrue(in_array($cols[4]->getText(), ['Operations', 'Dropbutton']));
+
+ // Check that rows contain new links for operations in Collabora.
+ $table_body = $assert_session->elementExists('css', 'tbody', $table);
+ $rows = $table_body->findAll('css', 'tr');
+ $i = 0;
+ foreach (Media::loadMultiple() as $media) {
+ $cols = $rows[$i]->findAll('css', 'td');
+ $this->assertEquals('Media ' . $i, $cols[0]->getText());
+ $this->assertEquals('Document', $cols[1]->getText());
+ $this->assertEquals('Yes', $cols[2]->getText());
+ $this->assertEquals('Anonymous', $cols[3]->getText());
+ $operation_links = $cols[4]->findAll('css', 'a');
+ $this->assertEquals('View in Collabora Online', $operation_links[0]->getText());
+ $this->assertEquals(
+ Url::fromRoute(
+ 'collabora-online.view',
+ [
+ 'media' => $media->id(),
+ ],
+ [
+ 'query' => [
+ 'destination' => "/group/{$group->id()}/media",
+ ],
+ ]
+ )->toString(),
+ $operation_links[0]->getAttribute('href')
+ );
+ $this->assertEquals('Edit in Collabora Online', $operation_links[1]->getText());
+ $this->assertEquals(
+ Url::fromRoute(
+ 'collabora-online.edit',
+ [
+ 'media' => $media->id(),
+ ],
+ [
+ 'query' => [
+ 'destination' => "/group/{$group->id()}/media",
+ ],
+ ]
+ )->toString(),
+ $operation_links[1]->getAttribute('href')
+ );
+ $i++;
+ }
+ }
+
+}
diff --git a/modules/collabora_online_group/tests/src/Kernel/AccessTest.php b/modules/collabora_online_group/tests/src/Kernel/AccessTest.php
new file mode 100644
index 00000000..b4d6e231
--- /dev/null
+++ b/modules/collabora_online_group/tests/src/Kernel/AccessTest.php
@@ -0,0 +1,332 @@
+installEntitySchema('file');
+ $this->installEntitySchema('media');
+ $this->installEntitySchema('group_role');
+ $this->installSchema('file', ['file_usage']);
+ }
+
+ /**
+ * Tests that access to Collabora group permissions is handled.
+ */
+ public function testCollaboraAccess(): void {
+ // Create group type, media type and enable plugin.
+ $group_type = $this->createGroupType();
+ $group_role = $this->createGroupRole([
+ 'group_type' => $group_type->id(),
+ 'scope' => PermissionScopeInterface::INSIDER_ID,
+ 'global_role' => RoleInterface::AUTHENTICATED_ID,
+ 'permissions' => [],
+ ]);
+ $this->createMediaType('file', ['id' => 'document']);
+ $this->createPluginRelation($group_type, 'group_media:document', [
+ 'group_cardinality' => 0,
+ 'entity_cardinality' => 1,
+ 'use_creation_wizard' => FALSE,
+ ]);
+
+ // Add group, media and relation between both.
+ $group = $this->createGroup(['type' => $group_type->id()]);
+ $media = $this->createMediaEntity('document');
+ $group->addRelationship($media, 'group_media:document');
+
+ // Iterate over each scenario.
+ foreach ($this->getTestScenarios() as $scenario_name => $scenario) {
+ // Apply status to media.
+ $media->set('status', $scenario['status'])->save();
+ // Set the current permissions for the existing role.
+ $group_role->set('permissions', $scenario['group_permissions'])->save();
+ // Create the user with the given permissions and as member of the
+ // group.
+ $user = $this->createUser($scenario['permissions']);
+ $group->addMember($user);
+ // Set user as owner if the scope is 'own'.
+ $owner = $scenario['scope'] === 'own' ? $user->id() : 0;
+ $media->setOwnerId($owner)->save();
+
+ // Check access.
+ $this->assertEquals(
+ $scenario['result'],
+ $media->access($scenario['operation'], $user),
+ sprintf('Access check failed for scenario: "%s"', $scenario_name)
+ );
+ }
+ }
+
+ /**
+ * Retrieves the scenarios to be tested.
+ *
+ * @return array
+ * An array of test scenarios.
+ */
+ protected function getTestScenarios(): array {
+ // The scenario keys contains values used for each scenario:
+ // 'operation:status:scope:global_permission:group_permission'.
+ return [
+ // Preview no permissions cases.
+ 'preview:published:any::' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => [],
+ 'operation' => 'preview in collabora',
+ 'status' => 1,
+ 'scope' => 'any',
+ ],
+ 'preview:published:own::' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => [],
+ 'operation' => 'preview in collabora',
+ 'status' => 1,
+ 'scope' => 'own',
+ ],
+ // The global permissions that would allow to preview, doesn't work
+ // in a media related to a group.
+ 'preview:published:any:preview:' => [
+ 'result' => FALSE,
+ 'permissions' => ['preview document in collabora'],
+ 'group_permissions' => [],
+ 'operation' => 'preview in collabora',
+ 'status' => 1,
+ 'scope' => 'any',
+ ],
+ 'preview:published:own:preview:' => [
+ 'result' => FALSE,
+ 'permissions' => ['preview document in collabora'],
+ 'group_permissions' => [],
+ 'operation' => 'preview in collabora',
+ 'status' => 1,
+ 'scope' => 'own',
+ ],
+ // User can only see published entities with the group preview
+ // permission.
+ 'preview:published:any::preview' => [
+ 'result' => TRUE,
+ 'permissions' => [],
+ 'group_permissions' => ['preview group_media:document in collabora'],
+ 'operation' => 'preview in collabora',
+ 'status' => 1,
+ 'scope' => 'any',
+ ],
+ 'preview:published:own::preview' => [
+ 'result' => TRUE,
+ 'permissions' => [],
+ 'group_permissions' => ['preview group_media:document in collabora'],
+ 'operation' => 'preview in collabora',
+ 'status' => 1,
+ 'scope' => 'own',
+ ],
+ 'preview:unpublished:any::preview' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => ['preview group_media:document in collabora'],
+ 'operation' => 'preview in collabora',
+ 'status' => 0,
+ 'scope' => 'any',
+ ],
+ 'preview:unpublished:own::preview' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => ['preview group_media:document in collabora'],
+ 'operation' => 'preview in collabora',
+ 'status' => 0,
+ 'scope' => 'own',
+ ],
+ // The global preview unpublished doesn't affect to medias related
+ // to a group.
+ 'preview:unpublished:own:preview_own_unpublished:' => [
+ 'result' => FALSE,
+ 'permissions' => ['preview own unpublished document in collabora'],
+ 'group_permissions' => [],
+ 'operation' => 'preview in collabora',
+ 'status' => 0,
+ 'scope' => 'own',
+ ],
+ // The group permission to preview own unpublished permission allows
+ // to see only entities with such properties.
+ 'preview:published:any::preview_own_unpublished' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => ['preview own unpublished group_media:document in collabora'],
+ 'operation' => 'preview in collabora',
+ 'status' => 1,
+ 'scope' => 'any',
+ ],
+ 'preview:published:own::preview_own_unpublished' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => ['preview own unpublished group_media:document in collabora'],
+ 'operation' => 'preview in collabora',
+ 'status' => 1,
+ 'scope' => 'own',
+ ],
+ 'preview:unpublished:own::preview_own_unpublished' => [
+ 'result' => TRUE,
+ 'permissions' => [],
+ 'group_permissions' => ['preview own unpublished group_media:document in collabora'],
+ 'operation' => 'preview in collabora',
+ 'status' => 0,
+ 'scope' => 'own',
+ ],
+ 'preview:unpublished:any::preview_own_unpublished' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => ['preview own unpublished group_media:document in collabora'],
+ 'operation' => 'preview in collabora',
+ 'status' => 0,
+ 'scope' => 'any',
+ ],
+ // Edit no permissions cases.
+ 'edit:published:any::' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => [],
+ 'operation' => 'edit in collabora',
+ 'status' => 1,
+ 'scope' => 'any',
+ ],
+ 'edit:published:own::' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => [],
+ 'operation' => 'edit in collabora',
+ 'status' => 1,
+ 'scope' => 'own',
+ ],
+ // The global permission doesn't grant access to edit in a group.
+ 'edit:published:any:edit_any:' => [
+ 'result' => FALSE,
+ 'permissions' => ['edit any document in collabora'],
+ 'group_permissions' => [],
+ 'operation' => 'edit in collabora',
+ 'status' => 1,
+ 'scope' => 'any',
+ ],
+ 'edit:published:own:edit_any:' => [
+ 'result' => FALSE,
+ 'permissions' => ['edit any document in collabora'],
+ 'group_permissions' => [],
+ 'operation' => 'edit in collabora',
+ 'status' => 1,
+ 'scope' => 'own',
+ ],
+ 'edit:published:own:edit_own:' => [
+ 'result' => FALSE,
+ 'permissions' => ['edit own document in collabora'],
+ 'group_permissions' => [],
+ 'operation' => 'edit in collabora',
+ 'status' => 1,
+ 'scope' => 'own',
+ ],
+ // Only users with edit any permission in a group can edit all.
+ 'edit:published:any::edit_any' => [
+ 'result' => TRUE,
+ 'permissions' => [],
+ 'group_permissions' => ['edit any group_media:document in collabora'],
+ 'operation' => 'edit in collabora',
+ 'status' => 1,
+ 'scope' => 'any',
+ ],
+ 'edit:published:own::edit_any' => [
+ 'result' => TRUE,
+ 'permissions' => [],
+ 'group_permissions' => ['edit any group_media:document in collabora'],
+ 'operation' => 'edit in collabora',
+ 'status' => 1,
+ 'scope' => 'own',
+ ],
+ 'edit:unpublished:any::edit_any' => [
+ 'result' => TRUE,
+ 'permissions' => [],
+ 'group_permissions' => ['edit any group_media:document in collabora'],
+ 'operation' => 'edit in collabora',
+ 'status' => 0,
+ 'scope' => 'any',
+ ],
+ 'edit:unpublished:own::edit_any' => [
+ 'result' => TRUE,
+ 'permissions' => [],
+ 'group_permissions' => ['edit any group_media:document in collabora'],
+ 'operation' => 'edit in collabora',
+ 'status' => 0,
+ 'scope' => 'own',
+ ],
+ // Or edit own permission for the entities the user owns.
+ 'edit:published:own::edit_own' => [
+ 'result' => TRUE,
+ 'permissions' => [],
+ 'group_permissions' => ['edit own group_media:document in collabora'],
+ 'operation' => 'edit in collabora',
+ 'status' => 1,
+ 'scope' => 'own',
+ ],
+ 'edit:unpublished:own::edit_own' => [
+ 'result' => TRUE,
+ 'permissions' => [],
+ 'group_permissions' => ['edit own group_media:document in collabora'],
+ 'operation' => 'edit in collabora',
+ 'status' => 0,
+ 'scope' => 'own',
+ ],
+ 'edit:published:any::edit_own' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => ['edit own group_media:document in collabora'],
+ 'operation' => 'edit in collabora',
+ 'status' => 1,
+ 'scope' => 'any',
+ ],
+ 'edit:unpublished:any::edit_own' => [
+ 'result' => FALSE,
+ 'permissions' => [],
+ 'group_permissions' => ['edit own group_media:document in collabora'],
+ 'operation' => 'edit in collabora',
+ 'status' => 0,
+ 'scope' => 'any',
+ ],
+ ];
+ }
+
+}
diff --git a/modules/collabora_online_group/tests/src/Kernel/PermissionTest.php b/modules/collabora_online_group/tests/src/Kernel/PermissionTest.php
new file mode 100644
index 00000000..4938b508
--- /dev/null
+++ b/modules/collabora_online_group/tests/src/Kernel/PermissionTest.php
@@ -0,0 +1,124 @@
+createGroupType();
+ $group_type_2 = $this->createGroupType();
+ $group_type_3 = $this->createGroupType();
+ $this->createMediaType('file', ['id' => 'document']);
+ $this->createMediaType('file', ['id' => 'spreadsheet']);
+
+ // Enable relation plugins in groups.
+ $this->createPluginRelation(
+ $group_type_1,
+ 'group_media:document',
+ [
+ 'group_cardinality' => 0,
+ 'entity_cardinality' => 1,
+ 'use_creation_wizard' => FALSE,
+ ]);
+ $this->createPluginRelation(
+ $group_type_2,
+ 'group_media:document',
+ [
+ 'group_cardinality' => 0,
+ 'entity_cardinality' => 1,
+ 'use_creation_wizard' => FALSE,
+ ]);
+ $this->createPluginRelation(
+ $group_type_2,
+ 'group_media:spreadsheet',
+ [
+ 'group_cardinality' => 0,
+ 'entity_cardinality' => 1,
+ 'use_creation_wizard' => FALSE,
+ ]);
+
+ // Check that permissions are generated for the groups.
+ // Save current permissions.
+ /** @var \Drupal\group\Access\GroupPermissionHandlerInterface $permission_handler */
+ $permission_handler = \Drupal::service('group.permissions');
+ $permissions_before_1 = $permission_handler->getPermissionsByGroupType($group_type_1);
+ $permissions_before_2 = $permission_handler->getPermissionsByGroupType($group_type_2);
+ $permissions_before_3 = $permission_handler->getPermissionsByGroupType($group_type_3);
+
+ // Get permissions difference after enabling the module.
+ $this->enableModules(['collabora_online_group']);
+ $permission_handler = \Drupal::service('group.permissions');
+ $permissions_after_1 = $permission_handler->getPermissionsByGroupType($group_type_1);
+ $new_permissions_1 = array_diff_key($permissions_after_1, $permissions_before_1);
+ ksort($new_permissions_1);
+ $permissions_after_2 = $permission_handler->getPermissionsByGroupType($group_type_2);
+ $new_permissions_2 = array_diff_key($permissions_after_2, $permissions_before_2);
+ ksort($new_permissions_2);
+ $permissions_after_3 = $permission_handler->getPermissionsByGroupType($group_type_3);
+ $new_permissions_3 = array_diff_key($permissions_after_3, $permissions_before_3);
+ ksort($new_permissions_3);
+
+ // The 'group_1' has only 'document' permissions.
+ $this->assertSame(
+ [
+ 'edit any group_media:document in collabora' => 'Entity: Edit any media item in collabora',
+ 'edit own group_media:document in collabora' => 'Entity: Edit own media item in collabora',
+ 'preview group_media:document in collabora' => 'Entity: Preview published media item in collabora',
+ 'preview own unpublished group_media:document in collabora' => 'Entity: Preview own unpublished media item in collabora',
+ ],
+ array_map(
+ fn($permission) => (string) $permission['title'],
+ $new_permissions_1,
+ ));
+ // The 'group_2' has 'document' and 'spreadsheet' permissions.
+ $this->assertSame(
+ [
+ 'edit any group_media:document in collabora' => 'Entity: Edit any media item in collabora',
+ 'edit any group_media:spreadsheet in collabora' => 'Entity: Edit any media item in collabora',
+ 'edit own group_media:document in collabora' => 'Entity: Edit own media item in collabora',
+ 'edit own group_media:spreadsheet in collabora' => 'Entity: Edit own media item in collabora',
+ 'preview group_media:document in collabora' => 'Entity: Preview published media item in collabora',
+ 'preview group_media:spreadsheet in collabora' => 'Entity: Preview published media item in collabora',
+ 'preview own unpublished group_media:document in collabora' => 'Entity: Preview own unpublished media item in collabora',
+ 'preview own unpublished group_media:spreadsheet in collabora' => 'Entity: Preview own unpublished media item in collabora',
+ ],
+ array_map(
+ fn($permission) => (string) $permission['title'],
+ $new_permissions_2,
+ ));
+ // The 'group_3' doesn't have any new permissions.
+ $this->assertSame(
+ [],
+ array_map(
+ fn($permission) => (string) $permission['title'],
+ $new_permissions_3,
+ ));
+ }
+
+}
diff --git a/modules/collabora_online_group/tests/src/Traits/GroupRelationTrait.php b/modules/collabora_online_group/tests/src/Traits/GroupRelationTrait.php
new file mode 100644
index 00000000..fb6c508d
--- /dev/null
+++ b/modules/collabora_online_group/tests/src/Traits/GroupRelationTrait.php
@@ -0,0 +1,46 @@
+entityTypeManager()->getDefinition($entity_type_id, FALSE) === NULL) {
+ $entity_type_id = 'group_content_type';
+ }
+
+ $entity = $this->entityTypeManager()
+ ->getStorage($entity_type_id)
+ ->createFromPlugin($group_type, $plugin_id, $values);
+ $entity->save();
+
+ return $entity;
+ }
+
+}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index b97f3661..9e02e0a3 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -19,8 +19,11 @@
-
+
./tests/src/
+
+ ./modules/collabora_online_group/tests/src/
+
diff --git a/src/Plugin/views/field/CollaboraEdit.php b/src/Plugin/views/field/CollaboraEdit.php
new file mode 100644
index 00000000..328ebc76
--- /dev/null
+++ b/src/Plugin/views/field/CollaboraEdit.php
@@ -0,0 +1,43 @@
+getEntity($row);
+
+ if ($entity === NULL) {
+ return NULL;
+ }
+
+ return CoolUtils::getEditorUrl($entity, TRUE);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultLabel(): TranslatableMarkup {
+ return $this->t('Edit in Collabora Online');
+ }
+
+}
diff --git a/src/Plugin/views/field/CollaboraPreview.php b/src/Plugin/views/field/CollaboraPreview.php
new file mode 100644
index 00000000..f636b359
--- /dev/null
+++ b/src/Plugin/views/field/CollaboraPreview.php
@@ -0,0 +1,43 @@
+getEntity($row);
+
+ if ($entity === NULL) {
+ return NULL;
+ }
+
+ return CoolUtils::getEditorUrl($entity, FALSE);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultLabel(): TranslatableMarkup {
+ return $this->t('View in Collabora Online');
+ }
+
+}
diff --git a/tests/modules/collabora_online_test/config/optional/views.view.test_collabora_links.yml b/tests/modules/collabora_online_test/config/optional/views.view.test_collabora_links.yml
new file mode 100644
index 00000000..bb423faf
--- /dev/null
+++ b/tests/modules/collabora_online_test/config/optional/views.view.test_collabora_links.yml
@@ -0,0 +1,275 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - collabora_online
+ - media
+ - user
+id: test_collabora_links
+label: "Test collabora links"
+module: views
+description: ""
+tag: ""
+base_table: media_field_data
+base_field: mid
+display:
+ default:
+ id: default
+ display_title: Default
+ display_plugin: default
+ position: 0
+ display_options:
+ fields:
+ name:
+ id: name
+ table: media_field_data
+ field: name
+ relationship: none
+ group_type: group
+ admin_label: ""
+ entity_type: media
+ entity_field: media
+ plugin_id: field
+ label: ""
+ exclude: false
+ alter:
+ alter_text: false
+ make_link: false
+ absolute: false
+ word_boundary: false
+ ellipsis: false
+ strip_tags: false
+ trim: false
+ 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: string
+ settings:
+ link_to_entity: true
+ 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
+ collabora_preview:
+ id: collabora_preview
+ table: media
+ field: collabora_preview
+ relationship: none
+ group_type: group
+ admin_label: ""
+ entity_type: media
+ plugin_id: media_collabora_preview
+ 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
+ text: ""
+ collabora_edit:
+ id: collabora_edit
+ table: media
+ field: collabora_edit
+ relationship: none
+ group_type: group
+ admin_label: ""
+ entity_type: media
+ plugin_id: media_collabora_edit
+ 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
+ text: ""
+ pager:
+ type: mini
+ options:
+ offset: 0
+ pagination_heading_level: h4
+ items_per_page: 10
+ total_pages: null
+ id: 0
+ tags:
+ next: ››
+ previous: ‹‹
+ 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
+ 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
+ access:
+ type: none
+ options: { }
+ cache:
+ type: tag
+ options: {}
+ empty: {}
+ sorts:
+ mid:
+ id: mid
+ table: media_field_data
+ field: mid
+ relationship: none
+ group_type: group
+ admin_label: ''
+ entity_type: media
+ entity_field: mid
+ plugin_id: standard
+ order: ASC
+ expose:
+ label: ''
+ field_identifier: ''
+ exposed: false
+ arguments: {}
+ filters: {}
+ style:
+ type: default
+ options:
+ grouping: {}
+ row_class: ""
+ default_row_class: true
+ uses_fields: false
+ row:
+ type: fields
+ options:
+ default_field_elements: true
+ inline: {}
+ separator: ""
+ hide_empty: false
+ query:
+ type: views_query
+ options:
+ query_comment: ""
+ disable_sql_rewrite: false
+ distinct: false
+ replica: false
+ query_tags: {}
+ relationships: {}
+ header: {}
+ footer: {}
+ display_extenders: {}
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - "languages:language_content"
+ - "languages:language_interface"
+ - url.query_args
+ - user.permissions
+ tags: {}
+ page_1:
+ id: page_1
+ display_title: Page
+ display_plugin: page
+ position: 1
+ display_options:
+ display_extenders: {}
+ path: test_collabora_links
+ cache_metadata:
+ max-age: -1
+ contexts:
+ - "languages:language_content"
+ - "languages:language_interface"
+ - url.query_args
+ - user.permissions
+ tags: {}
diff --git a/tests/src/Kernel/CollaboraMediaAccessTest.php b/tests/src/Kernel/CollaboraMediaAccessTest.php
index fd9a9c42..b9b6d814 100644
--- a/tests/src/Kernel/CollaboraMediaAccessTest.php
+++ b/tests/src/Kernel/CollaboraMediaAccessTest.php
@@ -8,10 +8,8 @@
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Url;
-use Drupal\file\Entity\File;
use Drupal\KernelTests\KernelTestBase;
-use Drupal\media\Entity\Media;
-use Drupal\media\MediaInterface;
+use Drupal\Tests\collabora_online\Traits\MediaCreationTrait;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\user\RoleInterface;
@@ -23,6 +21,7 @@ class CollaboraMediaAccessTest extends KernelTestBase {
use MediaTypeCreationTrait;
use UserCreationTrait;
+ use MediaCreationTrait;
/**
* {@inheritdoc}
@@ -272,32 +271,6 @@ protected function assertCollaboraMediaAccess(array $expected, AccountInterface
);
}
- /**
- * Creates a media entity with attached file.
- *
- * @param string $type
- * Media type.
- * @param array $values
- * Values for the media entity.
- *
- * @return \Drupal\media\MediaInterface
- * New media entity.
- */
- protected function createMediaEntity(string $type, array $values = []): MediaInterface {
- file_put_contents('public://test.txt', 'Hello test');
- $file = File::create([
- 'uri' => 'public://test.txt',
- ]);
- $file->save();
- $values += [
- 'bundle' => $type,
- 'field_media_file' => $file->id(),
- ];
- $media = Media::create($values);
- $media->save();
- return $media;
- }
-
/**
* Asserts that two values are the same when exported to yaml.
*
diff --git a/tests/src/Kernel/ViewsLinkFieldsTest.php b/tests/src/Kernel/ViewsLinkFieldsTest.php
new file mode 100644
index 00000000..10c4cbc2
--- /dev/null
+++ b/tests/src/Kernel/ViewsLinkFieldsTest.php
@@ -0,0 +1,174 @@
+installEntitySchema('file');
+ $this->installEntitySchema('media');
+ $this->installEntitySchema('user');
+ $this->installConfig(['user', 'views', 'collabora_online_test']);
+ $this->installSchema('file', ['file_usage']);
+ // Install user module to avoid user 1 permissions bypass.
+ \Drupal::moduleHandler()->loadInclude('user', 'install');
+ user_install();
+ }
+
+ /**
+ * Tests link fields.
+ */
+ public function testLinks(): void {
+ // User without permissions can't see links.
+ $this->doTestLinks(
+ [
+ 'preview' => [FALSE, FALSE, FALSE, FALSE],
+ 'edit' => [FALSE, FALSE, FALSE, FALSE],
+ ],
+ $this->createUser([])
+ );
+ // User with 'Preview' permission can see preview link.
+ $this->doTestLinks(
+ [
+ 'preview' => [TRUE, FALSE, TRUE, FALSE],
+ 'edit' => [FALSE, FALSE, FALSE, FALSE],
+ ],
+ $this->createUser([
+ 'preview document in collabora',
+ ])
+ );
+ // User with 'Preview own unpublished' permission can see preview link
+ // for unpublished entity they own.
+ $this->doTestLinks(
+ [
+ 'preview' => [FALSE, FALSE, FALSE, TRUE],
+ 'edit' => [FALSE, FALSE, FALSE, FALSE],
+ ],
+ $this->createUser([
+ 'preview own unpublished document in collabora',
+ ])
+ );
+ // User with 'Edit any' permission can see edit link.
+ $this->doTestLinks(
+ [
+ 'preview' => [FALSE, FALSE, FALSE, FALSE],
+ 'edit' => [TRUE, TRUE, TRUE, TRUE],
+ ],
+ $this->createUser([
+ 'edit any document in collabora',
+ ])
+ );
+ // User with 'Edit own' permission can see edit link for entities they
+ // own.
+ $this->doTestLinks(
+ [
+ 'preview' => [FALSE, FALSE, FALSE, FALSE],
+ 'edit' => [FALSE, FALSE, TRUE, TRUE],
+ ],
+ $this->createUser([
+ 'edit own document in collabora',
+ ])
+ );
+ }
+
+ /**
+ * Tests that links behave as expected.
+ *
+ * @param array $expected_results
+ * An associative array of expected results keyed by operation.
+ * @param \Drupal\Core\Session\AccountInterface $account
+ * The user account to be used to run the test.
+ */
+ protected function doTestLinks(array $expected_results, AccountInterface $account): void {
+ $this->setCurrentUser($account);
+ // Create medias: cover all combinations of status and ownership.
+ $this->createMediaEntity('document', [
+ 'uid' => $this->createUser(),
+ ]);
+ $this->createMediaEntity('document', [
+ 'uid' => $this->createUser(),
+ 'status' => 0,
+ ]);
+ $this->createMediaEntity('document', [
+ 'uid' => $account->id(),
+ ]);
+ $this->createMediaEntity('document', [
+ 'uid' => $account->id(),
+ 'status' => 0,
+ ]);
+
+ $view = Views::getView('test_collabora_links');
+ $view->preview();
+
+ $info = [
+ 'preview' => [
+ 'label' => 'View in Collabora Online',
+ 'field_id' => 'collabora_preview',
+ 'route' => 'collabora-online.view',
+ ],
+ 'edit' => [
+ 'label' => 'Edit in Collabora Online',
+ 'field_id' => 'collabora_edit',
+ 'route' => 'collabora-online.edit',
+ ],
+ ];
+
+ // Check each expected results for every media.
+ $i = 0;
+ foreach (Media::loadMultiple() as $media) {
+ foreach ($expected_results as $operation => $expected_result) {
+ $expected_link = '';
+ // The operations array contains results for each entity.
+ if ($expected_result[$i]) {
+ $path = Url::fromRoute($info[$operation]['route'], ['media' => $media->id()])->toString();
+ $expected_link = '' . $info[$operation]['label'] . '';
+ }
+ // We check the output: link HTML or empty (access denied).
+ $link = $view->style_plugin->getField($i, $info[$operation]['field_id']);
+ $this->assertEquals($expected_link, (string) $link);
+ }
+ $i++;
+ // Clean medias as we check results.
+ $media->delete();
+ }
+ }
+
+}
diff --git a/tests/src/Traits/MediaCreationTrait.php b/tests/src/Traits/MediaCreationTrait.php
new file mode 100644
index 00000000..32b59310
--- /dev/null
+++ b/tests/src/Traits/MediaCreationTrait.php
@@ -0,0 +1,43 @@
+ 'public://test.txt',
+ ]);
+ $file->save();
+ $values += [
+ 'bundle' => $type,
+ 'field_media_file' => $file->id(),
+ ];
+ $media = Media::create($values);
+ $media->save();
+
+ return $media;
+ }
+
+}