Skip to content

Commit

Permalink
Merge pull request #2 from nagi1/feature/support-365
Browse files Browse the repository at this point in the history
Support Microsoft 365
  • Loading branch information
nagi1 authored Sep 28, 2024
2 parents 61561e1 + e0c10b1 commit c08354d
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 23 deletions.
13 changes: 9 additions & 4 deletions config/wopi.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<?php

return [
/*
/*
* Managing documents differs a lot between apps, because of this reason
* this configration left empty to be implemented by the user There's
* plans to implement example storage manager in the future though.
*/
'document_manager' => null,

/*
/*
* Default UI langauge.
*/
'ui_language' => 'en-US',
Expand All @@ -29,15 +29,15 @@
* not have any validation logic. It's a great place to implement
* custom validation for the access_token and access_token_ttl.
*/
'wopi_request' => Nagi\LaravelWopi\Http\Requests\WopiRequest::class,
'wopi_request' => Nagi\LaravelWopi\Http\Requests\WopiRequest::class,

/*
* Here's you can define your middleware pipeline that every
* request from the wopi client will go through.
*/
'middleware' => [Nagi\LaravelWopi\Http\Middleware\ValidateProof::class],

/*
/*
* Collabora or Microsoft Office 365 or any WOPI client url.
*/
'client_url' => env('WOPI_CLIENT_URL', ''),
Expand Down Expand Up @@ -111,4 +111,9 @@
*/
'support_user_info' => false,

/*
* @see https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/online/discovery#placeholder-values
*/
'microsoft_365_url_placeholder_value_map' => [],

];
91 changes: 72 additions & 19 deletions src/Contracts/AbstractDocumentManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ abstract class AbstractDocumentManager
/**
* Preform look up for the file/document.
*
* @param string $fileId unique ID, Represent a single file and URL safe.
* @param string $fileId unique ID, Represent a single file and URL safe.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
Expand Down Expand Up @@ -271,46 +271,99 @@ public function generateUrl(string $lang = 'en-Us'): string

public function getUrlForAction(string $action, string $lang = 'en-US'): string
{
/** @var ConfigRepositoryInterface */
$config = app(ConfigRepositoryInterface::class);

$lang = empty($lang) ? $config->getDefaultUiLang() : $lang;

$extension = method_exists($this, 'extension')
? Str::replaceFirst('.', '', $this->extension())
: pathinfo($this->basename(), PATHINFO_EXTENSION);

$actionUrl = optional(Discovery::discoverAction($extension, $action));

$url = route('wopi.checkFileInfo', [
'file_id' => $this->id(),
]);

// todo handle microsoft office 365 <> placeholders
// example https://FFC-onenote.officeapps.live.com/hosting/WopiTestFrame.aspx?<ui=UI_LLCC&><rs=DC_LLCC&><dchat=DISABLE_CHAT&><hid=HOST_SESSION_ID&><sc=SESSION_CONTEXT&><wopisrc=WOPI_SOURCE&><IsLicensedUser=BUSINESS_USER&><testcategory=VALIDATOR_TEST_CATEGORY>
/** @var ConfigRepositoryInterface */
$config = app(ConfigRepositoryInterface::class);

$actionUrl = optional(Discovery::discoverAction($extension, $action));
$lang = empty($lang) ? $config->getDefaultUiLang() : $lang;

if (is_null($actionUrl['urlsrc'])) {
throw new Exception("Unsupported action \"{$action}\" for \"{$extension}\" extension.");
}

if (str($actionUrl['urlsrc'])->contains('officeapps.live.com')) {
return $this->processMicrosoftOffice365Url($actionUrl['urlsrc'], $url);
}

return "{$actionUrl['urlsrc']}lang={$lang}&WOPISrc={$url}";
}

protected function processMicrosoftOffice365Url(string $url, string $wopiSrc): string
{
/** @var ConfigRepositoryInterface */
$config = app(ConfigRepositoryInterface::class);

$lang = empty($lang) ? $config->getDefaultUiLang() : $lang;

$url = str($url);

// extract all placeholders <PLACEHOLDER_VALUE&> or <PLACEHOLDER_VALUE>
// https://excel.officeapps.live.com/x/_layouts/xlviewerinternal.aspx?<ui=UI_LLCC&><rs=DC_LLCC&><dchat=DISABLE_CHAT&><hid=HOST_SESSION_ID&><sc=SESSION_CONTEXT&><wopisrc=WOPI_SOURCE&><IsLicensedUser=BUSINESS_USER&><actnavid=ACTIVITY_NAVIGATION_ID&>

$reqiredReplaceMap = [
'UI_LLCC' => $lang,
'DC_LLCC' => $lang,
'WOPI_SOURCE' => $wopiSrc,
];

// extract it form the url and remove the required from them
$otherReplaceMap = config('wopi.microsoft_365_url_placeholder_value_map', []);

preg_match_all('/<([^>]*)>/', $url, $matches);

collect($matches[1])
// filter out nulls and falsy values
->filter()
->each(function (string $queryParamWithPlaceholder) use (&$url, &$reqiredReplaceMap, &$otherReplaceMap) {
foreach ($reqiredReplaceMap as $placeholder => $value) {
if (str($queryParamWithPlaceholder)->contains($placeholder)) {
$url = str($url)->replace($placeholder, $value);

return;
}
}

foreach ($otherReplaceMap as $placeholder => $value) {
if (str($queryParamWithPlaceholder)->contains($placeholder)) {
$url = str($url)->replace($placeholder, $value);

return;
}
}

// remove the rest of <PLACEHOLDER_VALUE> if not found
$url = str($url)->replace('<'.$queryParamWithPlaceholder.'>', '');
});

return $url->replace(['<', '>'], '')
->replaceLast('&', '')
->toString();
}

/**
* Get CheckfileInfo response proprites based
* on implemented interfaces/features.
*/
public function getResponseProprties(): array
{
return collect(static::$propertyMethodMapping)
->flatMap(function (string $methodName, string $propertyName) {
if (method_exists($this, $methodName)) {
return [
$propertyName => $this->$methodName(),
];
}
})
->filter(fn ($value) => $value !== null)
->toArray();
return collect(static::$propertyMethodMapping)
->flatMap(function (string $methodName, string $propertyName) {
if (method_exists($this, $methodName)) {
return [
$propertyName => $this->$methodName(),
];
}
})
->filter(fn ($value) => $value !== null)
->toArray();
}
}

0 comments on commit c08354d

Please sign in to comment.