diff --git a/collabora_online.module b/collabora_online.module
index 48ddad70..fd5e26ca 100644
--- a/collabora_online.module
+++ b/collabora_online.module
@@ -11,12 +11,14 @@
*/
use Drupal\collabora_online\CollaboraUrl;
-use Drupal\collabora_online\Cool\CoolUtils;
+use Drupal\collabora_online\Discovery\DiscoveryFetcherInterface;
+use Drupal\collabora_online\Exception\CollaboraNotAvailableException;
use Drupal\collabora_online\MediaHelperInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Utility\Error;
use Drupal\media\MediaInterface;
/**
@@ -116,15 +118,23 @@ function collabora_online_entity_operation(EntityInterface $entity): array {
],
];
- if (
- CoolUtils::canEditMimeType($type) &&
- $media->access('edit in collabora')
- ) {
- $entries['collabora_online_edit'] = [
- 'title' => t("Edit in Collabora Online"),
- 'weight' => 50,
- 'url' => CollaboraUrl::editMedia($media),
- ];
+ if ($media->access('edit in collabora')) {
+ /** @var \Drupal\collabora_online\Discovery\DiscoveryFetcherInterface $discovery_fetcher */
+ $discovery_fetcher = \Drupal::service(DiscoveryFetcherInterface::class);
+ try {
+ $discovery = $discovery_fetcher->getDiscovery();
+ $wopi_client_edit_url = $discovery->getWopiClientURL($type, 'edit');
+ if ($wopi_client_edit_url !== NULL) {
+ $entries['collabora_online_edit'] = [
+ 'title' => t("Edit in Collabora Online"),
+ 'weight' => 50,
+ 'url' => CollaboraUrl::editMedia($media),
+ ];
+ }
+ }
+ catch (CollaboraNotAvailableException $e) {
+ Error::logException(\Drupal::logger('cool'), $e);
+ }
}
return $entries;
diff --git a/src/Controller/ViewerController.php b/src/Controller/ViewerController.php
index c448652e..209738ae 100644
--- a/src/Controller/ViewerController.php
+++ b/src/Controller/ViewerController.php
@@ -17,6 +17,7 @@
use Drupal\collabora_online\Discovery\DiscoveryFetcherInterface;
use Drupal\collabora_online\Exception\CollaboraNotAvailableException;
use Drupal\collabora_online\Jwt\JwtTranscoderInterface;
+use Drupal\collabora_online\MediaHelperInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\AutowireTrait;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
@@ -43,6 +44,7 @@ public function __construct(
protected readonly DiscoveryFetcherInterface $discoveryFetcher,
protected readonly JwtTranscoderInterface $jwtTranscoder,
protected readonly RendererInterface $renderer,
+ protected readonly MediaHelperInterface $mediaHelper,
#[Autowire('logger.channel.collabora_online')]
protected readonly LoggerInterface $logger,
protected readonly ConfigFactoryInterface $configFactory,
@@ -67,10 +69,27 @@ public function __construct(
* Response suitable for iframe, without the usual page decorations.
*/
public function editor(MediaInterface $media, Request $request, $edit = FALSE): Response {
+ $file = $this->mediaHelper->getFileForMedia($media);
+ // Treat "no file" and "file without MIME type" the same.
+ $mimetype = $file?->getMimeType();
+ if ($mimetype === NULL) {
+ return new Response(
+ (string) $this->t('The Collabora Online editor/viewer is not available for media without a file attached.'),
+ Response::HTTP_BAD_REQUEST,
+ ['content-type' => 'text/plain'],
+ );
+ }
try {
- // @todo Get client url for the correct MIME type.
$discovery = $this->discoveryFetcher->getDiscovery();
- $wopi_client_url = $discovery->getWopiClientURL();
+
+ $wopi_client_url = $edit
+ ? $discovery->getWopiClientURL($mimetype, 'edit')
+ : ($discovery->getWopiClientURL($mimetype, 'view')
+ // With the typical discovery.xml from Collabora, some MIME types that
+ // are viewable have an 'edit' or 'view_comment' action but no 'view'
+ // action.
+ ?? $discovery->getWopiClientURL($mimetype, 'edit')
+ ?? $discovery->getWopiClientURL($mimetype, 'view_comment'));
}
catch (CollaboraNotAvailableException $e) {
$this->logger->warning(
diff --git a/src/Cool/CoolUtils.php b/src/Cool/CoolUtils.php
deleted file mode 100644
index 2956dcf7..00000000
--- a/src/Cool/CoolUtils.php
+++ /dev/null
@@ -1,43 +0,0 @@
- TRUE,
- 'application/x-iwork-pages-sffpages' => TRUE,
- 'application/x-iwork-numbers-sffnumbers' => TRUE,
- ];
-
- /**
- * Determines if a MIME type is supported for editing.
- *
- * @param string $mimetype
- * File MIME type.
- *
- * @return bool
- * TRUE if the MIME type is supported for editing.
- * FALSE if the MIME type can only be opened as read-only.
- */
- public static function canEditMimeType(string $mimetype) {
- return !array_key_exists($mimetype, static::READ_ONLY);
- }
-
-}
diff --git a/src/Discovery/Discovery.php b/src/Discovery/Discovery.php
index baec6a6a..a990c621 100644
--- a/src/Discovery/Discovery.php
+++ b/src/Discovery/Discovery.php
@@ -32,8 +32,12 @@ public function __construct(
/**
* {@inheritdoc}
*/
- public function getWopiClientURL(string $mimetype = 'text/plain'): ?string {
- $result = $this->parsedXml->xpath(sprintf('/wopi-discovery/net-zone/app[@name=\'%s\']/action', $mimetype));
+ public function getWopiClientURL(string $mimetype, string $action): ?string {
+ $result = $this->parsedXml->xpath(sprintf(
+ "/wopi-discovery/net-zone/app[@name='%s']/action[@name='%s']",
+ $mimetype,
+ $action,
+ ));
if (empty($result[0]['urlsrc'][0])) {
return NULL;
}
diff --git a/src/Discovery/DiscoveryInterface.php b/src/Discovery/DiscoveryInterface.php
index b6fd73a3..4fe3ed80 100644
--- a/src/Discovery/DiscoveryInterface.php
+++ b/src/Discovery/DiscoveryInterface.php
@@ -25,11 +25,15 @@ interface DiscoveryInterface {
* @param string $mimetype
* Mime type for which to get the WOPI client URL.
* This refers to config entries in the discovery.xml file.
+ * @param string $action
+ * Name of the action/operation for which to get the url.
+ * Typical values are 'view', 'edit' or 'view_comment'.
*
* @return string|null
- * The WOPI client URL, or NULL if none provided for the MIME type.
+ * The WOPI client URL, or NULL if none provided for the MIME type and
+ * operation.
*/
- public function getWopiClientURL(string $mimetype = 'text/plain'): ?string;
+ public function getWopiClientURL(string $mimetype, string $action): ?string;
/**
* Gets the public key used for proofing.
diff --git a/tests/fixtures/discovery.mimetypes.xml b/tests/fixtures/discovery.mimetypes.xml
index e8458fa6..d9842966 100644
--- a/tests/fixtures/discovery.mimetypes.xml
+++ b/tests/fixtures/discovery.mimetypes.xml
@@ -7,8 +7,14 @@
+
+
+
-
+
+
+
+
diff --git a/tests/fixtures/discovery.xml b/tests/fixtures/discovery.xml
new file mode 100644
index 00000000..6d058aba
--- /dev/null
+++ b/tests/fixtures/discovery.xml
@@ -0,0 +1,456 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/src/ExistingSite/FetchClientUrlTest.php b/tests/src/ExistingSite/FetchClientUrlTest.php
index e07cbd96..d3f4b368 100644
--- a/tests/src/ExistingSite/FetchClientUrlTest.php
+++ b/tests/src/ExistingSite/FetchClientUrlTest.php
@@ -41,7 +41,7 @@ public function testFetchClientUrl(): void {
/** @var \Drupal\collabora_online\Discovery\DiscoveryFetcherInterface $discovery_fetcher */
$discovery_fetcher = \Drupal::service(DiscoveryFetcherInterface::class);
$discovery = $discovery_fetcher->getDiscovery();
- $client_url = $discovery->getWopiClientURL();
+ $client_url = $discovery->getWopiClientURL('text/plain', 'edit');
$this->assertNotNull($client_url);
// The protocol, domain and port are known when this test runs in the
// docker-compose setup.
diff --git a/tests/src/Kernel/DiscoveryFetcherTest.php b/tests/src/Kernel/DiscoveryFetcherTest.php
index a3252ca3..f9c01fcd 100644
--- a/tests/src/Kernel/DiscoveryFetcherTest.php
+++ b/tests/src/Kernel/DiscoveryFetcherTest.php
@@ -117,7 +117,7 @@ public function testGetDiscovery(): void {
$discovery = $fetcher->getDiscovery();
$this->assertSame(
'http://collabora.test:9980/browser/61cf2b4/cool.html?',
- $discovery->getWopiClientURL(),
+ $discovery->getWopiClientURL('text/plain', 'view'),
);
$this->assertSame(
[
diff --git a/tests/src/Unit/CollaboraDiscoveryTest.php b/tests/src/Unit/CollaboraDiscoveryTest.php
index 19b7aa22..95c995ef 100644
--- a/tests/src/Unit/CollaboraDiscoveryTest.php
+++ b/tests/src/Unit/CollaboraDiscoveryTest.php
@@ -6,6 +6,7 @@
use Drupal\collabora_online\Discovery\Discovery;
use Drupal\collabora_online\Discovery\DiscoveryInterface;
+use Drupal\Core\Serialization\Yaml;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\ErrorHandler\ErrorHandler;
@@ -24,16 +25,171 @@ public function testWopiClientUrl(): void {
$discovery = $this->getDiscoveryFromFile($file);
$this->assertSame(
'http://collabora.test:9980/browser/61cf2b4/cool.html?',
- $discovery->getWopiClientURL(),
+ $discovery->getWopiClientURL('text/plain', 'view'),
);
$this->assertSame(
'http://spreadsheet.collabora.test:9980/browser/61cf2b4/cool.html?',
- $discovery->getWopiClientURL('text/spreadsheet'),
+ $discovery->getWopiClientURL('text/spreadsheet', 'edit'),
);
// Test unknown mime type.
- $this->assertNull(
- $discovery->getWopiClientURL('text/unknown'),
+ $this->assertNull($discovery->getWopiClientURL('text/unknown', 'view'));
+ $this->assertSame(
+ 'http://csv.collabora.test:9980/browser/61cf2b4/cool.html?',
+ $discovery->getWopiClientURL('text/csv', 'edit'),
+ );
+ $this->assertSame(
+ 'http://view.csv.collabora.test:9980/browser/61cf2b4/cool.html?',
+ $discovery->getWopiClientURL('text/csv', 'view'),
);
+ // Test the default MIME type 'text/plain' which has only 'edit' action in
+ // the example file, but no 'view' action.
+ $this->assertNull($discovery->getWopiClientURL('text/plain', 'edit'));
+ $this->assertNotNull($discovery->getWopiClientURL('text/plain', 'view'));
+ // Test a MIME type with no action name specified.
+ // This does not occur in the known discovery.xml, but we still want a
+ // well-defined behavior in that case.
+ $this->assertNull($discovery->getWopiClientURL('image/png', 'edit'));
+ $this->assertNull($discovery->getWopiClientURL('image/png', 'view'));
+ }
+
+ /**
+ * Tests which MIME types are supported in a realistic discovery.xml.
+ *
+ * That file was generated with Collabora, but may not be the same in the
+ * latest version.
+ */
+ public function testRealisticDiscoveryXml(): void {
+ $file = dirname(__DIR__, 2) . '/fixtures/discovery.xml';
+ $xml = file_get_contents($file);
+ $this->assertSame(98, preg_match_all('@]+)* name="([^"]+)"@', $xml, $matches));
+ $this->assertSame('application/vnd.ms-excel', $matches[2][9]);
+ $mimetypes = array_unique($matches[2]);
+ $mimetypes = array_diff($mimetypes, ['Capabilities']);
+ sort($mimetypes);
+ $discovery = $this->getDiscoveryFromXml($xml);
+ $known_url = 'http://collabora.test:9980/browser/61cf2b4/cool.html?';
+ $supported_action_types = [];
+ foreach ($mimetypes as $mimetype) {
+ $type_supported_actions = [];
+ foreach (['edit', 'view_comment', 'view'] as $action) {
+ $url = $discovery->getWopiClientURL($mimetype, $action);
+ if ($url !== NULL) {
+ $this->assertSame($known_url, $url);
+ $type_supported_actions[] = $action;
+ }
+ }
+ sort($type_supported_actions);
+ $supported_action_types[implode(',', $type_supported_actions)][] = $mimetype;
+ }
+ ksort($supported_action_types);
+ $this->assertSame([
+ '' => [
+ 'application/vnd.oasis.opendocument.formula',
+ 'application/vnd.sun.xml.math',
+ 'math',
+ ],
+ 'edit' => [
+ 'application/msword',
+ 'application/vnd.ms-excel',
+ 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+ 'application/vnd.ms-excel.sheet.macroEnabled.12',
+ 'application/vnd.ms-powerpoint',
+ 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 'application/vnd.ms-word.document.macroEnabled.12',
+ 'application/vnd.oasis.opendocument.chart',
+ 'application/vnd.oasis.opendocument.graphics',
+ 'application/vnd.oasis.opendocument.graphics-flat-xml',
+ 'application/vnd.oasis.opendocument.presentation',
+ 'application/vnd.oasis.opendocument.presentation-flat-xml',
+ 'application/vnd.oasis.opendocument.spreadsheet',
+ 'application/vnd.oasis.opendocument.spreadsheet-flat-xml',
+ 'application/vnd.oasis.opendocument.text',
+ 'application/vnd.oasis.opendocument.text-flat-xml',
+ 'application/vnd.oasis.opendocument.text-master',
+ 'application/vnd.oasis.opendocument.text-web',
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'application/vnd.sun.xml.draw',
+ 'application/x-dbase',
+ 'application/x-dif-document',
+ 'text/csv',
+ 'text/plain',
+ 'text/rtf',
+ 'text/spreadsheet',
+ 'writer-web',
+ ],
+ 'edit,view' => [
+ 'calc',
+ 'impress',
+ 'writer',
+ 'writer-global',
+ ],
+ 'edit,view,view_comment' => [
+ 'draw',
+ ],
+ 'view' => [
+ 'application/clarisworks',
+ 'application/coreldraw',
+ 'application/macwriteii',
+ 'application/vnd.lotus-1-2-3',
+ 'application/vnd.ms-excel.template.macroEnabled.12',
+ 'application/vnd.ms-powerpoint.template.macroEnabled.12',
+ 'application/vnd.ms-visio.drawing',
+ 'application/vnd.ms-word.template.macroEnabled.12',
+ 'application/vnd.ms-works',
+ 'application/vnd.oasis.opendocument.graphics-template',
+ 'application/vnd.oasis.opendocument.presentation-template',
+ 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'application/vnd.oasis.opendocument.text-master-template',
+ 'application/vnd.oasis.opendocument.text-template',
+ 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'application/vnd.sun.xml.calc',
+ 'application/vnd.sun.xml.calc.template',
+ 'application/vnd.sun.xml.chart',
+ 'application/vnd.sun.xml.draw.template',
+ 'application/vnd.sun.xml.impress',
+ 'application/vnd.sun.xml.impress.template',
+ 'application/vnd.sun.xml.writer',
+ 'application/vnd.sun.xml.writer.global',
+ 'application/vnd.sun.xml.writer.template',
+ 'application/vnd.visio',
+ 'application/vnd.visio2013',
+ 'application/vnd.wordperfect',
+ 'application/x-abiword',
+ 'application/x-aportisdoc',
+ 'application/x-fictionbook+xml',
+ 'application/x-gnumeric',
+ 'application/x-hwp',
+ 'application/x-iwork-keynote-sffkey',
+ 'application/x-iwork-numbers-sffnumbers',
+ 'application/x-iwork-pages-sffpages',
+ 'application/x-mspublisher',
+ 'application/x-mswrite',
+ 'application/x-pagemaker',
+ 'application/x-sony-bbeb',
+ 'application/x-t602',
+ 'image/bmp',
+ 'image/cgm',
+ 'image/gif',
+ 'image/jpeg',
+ 'image/jpg',
+ 'image/png',
+ 'image/svg+xml',
+ 'image/tiff',
+ 'image/vnd.dxf',
+ 'image/x-emf',
+ 'image/x-freehand',
+ 'image/x-wmf',
+ 'image/x-wpg',
+ ],
+ 'view_comment' => [
+ 'application/pdf',
+ ],
+ ], $supported_action_types);
}
/**