Merge branch 'refactor/sync-wopi' into 'master'

Refactor and synchronize WOPI packages

See merge request Chill-Projet/chill-bundles!157
This commit is contained in:
Julien Fastré 2021-09-29 12:05:31 +00:00
commit c53636c40b
10 changed files with 363 additions and 204 deletions

View File

@ -7,6 +7,7 @@ charset = utf-8
end_of_line = LF
insert_final_newline = true
trim_trailing_whitespace = true
indent_size = 4
[*.{php,html,twig}]
indent_style = space

View File

@ -22,6 +22,7 @@
"league/csv": "^9.7.1",
"nyholm/psr7": "^1.4",
"phpoffice/phpspreadsheet": "^1.16",
"ramsey/uuid-doctrine": "^1.7",
"sensio/framework-extra-bundle": "^5.5",
"symfony/asset": "4.*",
"symfony/browser-kit": "^5.2",
@ -29,6 +30,7 @@
"symfony/expression-language": "4.*",
"symfony/form": "4.*",
"symfony/intl": "4.*",
"symfony/mime": "^4 || ^5",
"symfony/monolog-bundle": "^3.5",
"symfony/security-bundle": "4.*",
"symfony/serializer": "^5.2",

View File

@ -7,7 +7,9 @@ namespace Chill\DocStoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface;
use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists;
use ChampsLibres\WopiLib\Contract\Entity\Document;
use DateTimeInterface;
use Ramsey\Uuid\Uuid;
use Symfony\Component\Serializer\Annotation as Serializer;
/**
@ -19,7 +21,7 @@ use Symfony\Component\Serializer\Annotation as Serializer;
* message="The file is not stored properly"
* )
*/
class StoredObject implements AsyncFileInterface
class StoredObject implements AsyncFileInterface, Document
{
/**
* @ORM\Id()
@ -47,6 +49,11 @@ class StoredObject implements AsyncFileInterface
*/
private array $iv = [];
/**
* @ORM\Column(type="uuid", unique=true)
*/
private Uuid $uuid;
/**
* @ORM\Column(type="datetime", name="creation_date")
* @Serializer\Groups({"read"})
@ -68,6 +75,7 @@ class StoredObject implements AsyncFileInterface
public function __construct()
{
$this->creationDate = new \DateTime();
$this->uuid = Uuid::uuid4();
}
public function getId()
@ -155,5 +163,13 @@ class StoredObject implements AsyncFileInterface
return $this;
}
public function getUuid(): Uuid
{
return $this->uuid;
}
public function getWopiDocId(): string
{
return (string) $this->uuid;
}
}

View File

@ -3,9 +3,12 @@
"description": "A Chill bundle to store documents",
"type": "symfony-bundle",
"autoload": {
"psr-4": { "Chill\\DocStoreBundle\\" : "" }
"psr-4": {
"Chill\\DocStoreBundle\\": ""
}
},
"require": {
"symfony/mime": "^4 || ^5"
},
"license": "AGPL-3.0"
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Chill\Migrations\DocStore;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20210928182542 extends AbstractMigration
{
public function getDescription(): string
{
return 'Create UUID column on StoredObject table.';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE chill_doc.stored_object ADD uuid UUID NOT NULL');
$this->addSql('CREATE UNIQUE INDEX UNIQ_49604E36D17F50A6 ON chill_doc.stored_object (uuid)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP INDEX UNIQ_49604E36D17F50A6');
$this->addSql('ALTER TABLE chill_doc.stored_object DROP uuid');
}
}

View File

@ -19,7 +19,7 @@
"php": ">= 7.4",
"champs-libres/wopi-bundle": "dev-master",
"nyholm/psr7": "^1.4",
"php-opencloud/openstack": "^3.2.1"
"symfony/mime": "^4 || ^5"
},
"autoload": {
"psr-4": {

View File

@ -9,9 +9,10 @@ declare(strict_types=1);
namespace Chill\WopiBundle\Controller;
use ChampsLibres\WopiLib\Configuration\WopiConfigurationInterface;
use ChampsLibres\WopiLib\Discovery\WopiDiscoveryInterface;
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
use ChampsLibres\WopiLib\Contract\Service\Configuration\ConfigurationInterface;
use ChampsLibres\WopiLib\Contract\Service\Discovery\DiscoveryInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\WopiBundle\Service\Controller\ResponderInterface;
use Exception;
use loophp\psr17\Psr17Interface;
@ -23,11 +24,11 @@ use Symfony\Component\Security\Core\Security;
final class Test
{
private StoredObjectRepository $storedObjectRepository;
private DiscoveryInterface $wopiDiscovery;
private WopiDiscoveryInterface $wopiDiscovery;
private DocumentManagerInterface $documentManager;
private WopiConfigurationInterface $wopiConfiguration;
private ConfigurationInterface $wopiConfiguration;
private ResponderInterface $responder;
@ -38,15 +39,15 @@ final class Test
private RouterInterface $router;
public function __construct(
StoredObjectRepository $storedObjectRepository,
WopiConfigurationInterface $wopiConfiguration,
WopiDiscoveryInterface $wopiDiscovery,
ConfigurationInterface $wopiConfiguration,
DiscoveryInterface $wopiDiscovery,
DocumentManagerInterface $documentManager,
ResponderInterface $responder,
Security $security,
Psr17Interface $psr17,
RouterInterface $router
) {
$this->storedObjectRepository = $storedObjectRepository;
$this->documentManager = $documentManager;
$this->wopiConfiguration = $wopiConfiguration;
$this->wopiDiscovery = $wopiDiscovery;
$this->responder = $responder;
@ -58,11 +59,11 @@ final class Test
public function __invoke(string $fileId): Response
{
$configuration = $this->wopiConfiguration->jsonSerialize();
$storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]);
/** @var StoredObject $storedObject */
$storedObject = $this->documentManager->findByDocumentId($fileId);
if (null === $storedObject) {
throw new NotFoundHttpException(sprintf('Unable to find object named %s', $fileId));
throw new NotFoundHttpException(sprintf('Unable to find object %s', $fileId));
}
if ([] === $discoverExtension = $this->wopiDiscovery->discoverMimeType($storedObject->getType())) {
@ -83,7 +84,7 @@ final class Test
->generate(
'checkFileInfo',
[
'fileId' => $storedObject->getFilename(),
'fileId' => $this->documentManager->getDocumentId($storedObject),
],
UrlGeneratorInterface::ABSOLUTE_URL
),

View File

@ -10,8 +10,10 @@ declare(strict_types=1);
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
use ChampsLibres\WopiLib\Service\Contract\WopiInterface;
use Chill\WopiBundle\Service\Wopi\ChillWopi;
use ChampsLibres\WopiBundle\Service\Wopi as CLWopi;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\WopiBundle\Service\Wopi\ChillDocumentManager;
return static function (ContainerConfigurator $container) {
$services = $container
@ -30,8 +32,14 @@ return static function (ContainerConfigurator $container) {
->tag('controller.service_arguments');
$services
->alias(WopiInterface::class, ChillWopi::class);
->set(ChillWopi::class)
->decorate(CLWopi::class)
->arg('$wopi', service('.inner'));
$services
->alias(DocumentManagerInterface::class, ChillDocumentManager::class);
// TODO: Move this into the async bundle (low priority)
$services
->alias(TempUrlGeneratorInterface::class, 'async_uploader.temp_url_generator');
};

View File

@ -0,0 +1,243 @@
<?php
/**
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Chill\WopiBundle\Service\Wopi;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
use ChampsLibres\WopiLib\Contract\Entity\Document;
use ChampsLibres\WopiLib\Contract\Service\DocumentLockManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
use DateTimeInterface;
use Doctrine\ORM\EntityManagerInterface;
use Error;
use loophp\psr17\Psr17Interface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Throwable;
final class ChillDocumentManager implements DocumentManagerInterface
{
private DocumentLockManagerInterface $documentLockManager;
private EntityManagerInterface $entityManager;
private HttpClientInterface $httpClient;
private Psr17Interface $psr17;
private RequestInterface $request;
private StoredObjectRepository $storedObjectRepository;
private TempUrlGeneratorInterface $tempUrlGenerator;
public function __construct(
DocumentLockManagerInterface $documentLockManager,
EntityManagerInterface $entityManager,
HttpClientInterface $httpClient,
Psr17Interface $psr17,
StoredObjectRepository $storedObjectRepository,
TempUrlGeneratorInterface $tempUrlGenerator,
HttpMessageFactoryInterface $httpMessageFactory,
RequestStack $requestStack
) {
$this->entityManager = $entityManager;
$this->psr17 = $psr17;
$this->storedObjectRepository = $storedObjectRepository;
$this->documentLockManager = $documentLockManager;
$this->tempUrlGenerator = $tempUrlGenerator;
$this->httpClient = $httpClient;
$this->request = $httpMessageFactory->createRequest($requestStack->getCurrentRequest());
}
public function create(array $data): Document
{
/** @var StoredObject $document */
$document = (new ObjectNormalizer())->denormalize([], StoredObject::class);
// Mime types / extension handling.
$mimeTypes = new MimeTypes();
$mimeTypes->getMimeTypes($data['extension']);
$document->setType(reset($mimeTypes));
$document->setFilename($data['name']);
$this->entityManager->persist($document);
$this->entityManager->flush($document);
// TODO : Ask proper mapping.
// Available: basename, name, extension, content, size
$this->setContent($document, $data['content']);
return $document;
}
public function deleteLock(Document $document): void {
$this->documentLockManager->deleteLock($document, $this->request);
}
/**
* @param string $documentFilename without extension !
*/
public function findByDocumentFilename(string $documentFilename): ?Document {
return $this->storedObjectRepository->findOneBy(
[
'filename' => $documentFilename,
]
);
}
public function findByDocumentId(string $documentId): ?Document {
return $this->storedObjectRepository->findOneBy(
[
'uuid' => $documentId,
]
);
}
/**
* @param StoredObject $document
*/
public function getCreationDate(Document $document): DateTimeInterface
{
return $document->getCreationDate();
}
/**
* @param StoredObject $document
*/
public function getLastModifiedDate(Document $document): DateTimeInterface
{
// TODO: Add column 'LastModifiedDate' in StoredObject entity
return $document->getCreationDate();
}
public function getLock(Document $document): string {
return $this->documentLockManager->getLock($document, $this->request);
}
public function getVersion(Document $document): string {
// TODO ?
return '0';
}
public function hasLock(Document $document): bool {
return $this->documentLockManager->hasLock($document, $this->request);
}
public function lock(Document $document, string $lock): void {
$this->documentLockManager->setLock($document, $lock, $this->request);
}
public function remove(Document $document): void {
$entityIsDeleted = false;
try {
$this->entityManager->remove($document);
$entityIsDeleted = true;
} catch (Throwable $e) {
$entityIsDeleted = false;
}
if ($entityIsDeleted === true) {
$this->deleteContent($document);
}
}
public function write(Document $document, array $properties = []): void
{
$this->setContent($document, $properties['content']);
}
/**
* @param StoredObject $document
*
* @return string The document filename with its extension.
*/
public function getBasename(Document $document): string {
$exts = (new MimeTypes())->getExtensions($document->getType());
if ([] === $exts) {
throw new Error('Unknown mimetype for stored document.');
}
return sprintf('%s.%s', $document->getFilename(), reset($exts));
}
/**
* @param StoredObject $document
*/
public function getDocumentId(Document $document): string {
return (string) $document->getUuid();
}
public function getSha256(Document $document): string {
return base64_encode(hash('sha256', $this->getContent($document)));
}
public function getSize(Document $document): int {
return strlen(stream_get_contents($this->read($document)));
}
public function read(Document $document): StreamInterface {
return $this
->psr17
->createStream($this->getContent($document));
}
private function deleteContent(StoredObject $storedObject): string
{
/** @var StdClass $object */
$object = $this->tempUrlGenerator->generate('DELETE', $storedObject->getFilename());
$response = $this->httpClient->request('DELETE', $object->url);
if (200 !== $response->getStatusCode())
{
throw new Error('Unable to delete stored object.');
}
}
private function getContent(StoredObject $storedObject): string
{
/** @var StdClass $object */
$object = $this->tempUrlGenerator->generate('GET', $storedObject->getFilename());
$response = $this->httpClient->request('GET', $object->url);
if (200 !== $response->getStatusCode())
{
throw new Error('Unable to retrieve stored object.');
}
return $response->getContent();
}
private function setContent(StoredObject $storedObject, string $content): void
{
// TODO: Add strict typing in champs-libres/async-uploader-bundle
/** @var StdClass $object */
$object = $this->tempUrlGenerator->generate('PUT', $storedObject->getFilename());
$response = $this->httpClient->request('PUT', $object->url, ['body' => $content]);
if (200 !== $response->getStatusCode())
{
throw new Error('Unable to save stored object.');
}
}
}

View File

@ -9,46 +9,28 @@ declare(strict_types=1);
namespace Chill\WopiBundle\Service\Wopi;
use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface;
use ChampsLibres\WopiLib\Discovery\WopiDiscoveryInterface;
use ChampsLibres\WopiLib\Service\Contract\WopiInterface;
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
use Exception;
use ChampsLibres\WopiLib\Contract\Service\DocumentManagerInterface;
use ChampsLibres\WopiLib\Contract\Service\WopiInterface;
use loophp\psr17\Psr17Interface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
final class ChillWopi implements WopiInterface
{
private DocumentManagerInterface $documentManager;
private Psr17Interface $psr17;
private WopiDiscoveryInterface $wopiDiscovery;
private StoredObjectRepository $storedObjectRepository;
private ClientInterface $httpClient;
private TempUrlGeneratorInterface $tempUrlGenerator;
private UserProviderInterface $userProvider;
private WopiInterface $wopi;
public function __construct(
DocumentManagerInterface $documentManager,
Psr17Interface $psr17,
WopiDiscoveryInterface $wopiDiscovery,
StoredObjectRepository $storedObjectRepository,
ClientInterface $httpClient,
TempUrlGeneratorInterface $tempUrlGenerator,
UserProviderInterface $userProvider
WopiInterface $wopi
) {
$this->documentManager = $documentManager;
$this->psr17 = $psr17;
$this->wopiDiscovery = $wopiDiscovery;
$this->storedObjectRepository = $storedObjectRepository;
$this->httpClient = $httpClient;
$this->tempUrlGenerator = $tempUrlGenerator;
$this->userProvider = $userProvider;
$this->wopi = $wopi;
}
public function checkFileInfo(
@ -56,59 +38,31 @@ final class ChillWopi implements WopiInterface
?string $accessToken,
RequestInterface $request
): ResponseInterface {
try {
$user = $this->userProvider->loadUserByUsername($accessToken);
} catch (UsernameNotFoundException $e) {
return $this
->psr17
->createResponse(401);
}
$response = $this->wopi->checkFileInfo($fileId, $accessToken, $request);
$document = $this->documentManager->findByDocumentId($fileId);
$storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]);
$body = json_decode((string) $response->getBody(), true);
if (null === $storedObject) {
throw new Exception(sprintf('Unable to find object named %s', $fileId));
}
$mimeType = $storedObject->getType();
if ([] === $this->wopiDiscovery->discoverMimeType($mimeType)) {
throw new Exception(sprintf('Unable to find mime type %s', $mimeType));
}
return $this
->psr17
->createResponse()
->withHeader('Content-Type', 'application/json')
->withBody($this->psr17->createStream((string) json_encode(
[
'BaseFileName' => $storedObject->getFilename(),
'OwnerId' => uniqid(),
'Size' => 0,
'UserId' => uniqid(),
// 'Version' => 'v' . uniqid(),
'ReadOnly' => false,
'UserCanWrite' => true,
'UserCanNotWriteRelative' => true,
'SupportsLocks' => false,
'UserFriendlyName' => sprintf('User %s', $user->getUsername()),
'UserExtraInfo' => [],
'LastModifiedTime' => date('Y-m-d\TH:i:s.u\Z', $storedObject->getCreationDate()->getTimestamp()),
'CloseButtonClosesWindow' => true,
'EnableInsertRemoteImage' => true,
'EnableShare' => false,
'SupportsUpdate' => true,
'SupportsRename' => false,
'DisablePrint' => false,
'DisableExport' => false,
'DisableCopy' => false,
]
)));
return $response
->withBody(
$this
->psr17
->createStream(
(string) json_encode(
$body +
[
'Version' => sprintf('v%s', $this->documentManager->getVersion($document)),
// TODO: Add column 'LastModifiedDate' in StoredObject entity
'LastModifiedTime' => $this->documentManager->getLastModifiedDate($document)->format('Y-m-d\TH:i:s.uP'),
]
)
)
);
}
public function deleteFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
{
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->deleteFile($fileId, $accessToken, $request);
}
public function enumerateAncestors(
@ -116,57 +70,17 @@ final class ChillWopi implements WopiInterface
?string $accessToken,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->enumerateAncestors($fileId, $accessToken, $request);
}
public function getFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
{
try {
$user = $this->userProvider->loadUserByUsername($accessToken);
} catch (UsernameNotFoundException $e) {
return $this
->psr17
->createResponse(401);
}
$storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]);
if (null === $storedObject) {
return $this
->psr17
->createResponse(404);
}
// TODO: Add strict typing in champs-libres/async-uploader-bundle
/** @var StdClass $object */
$object = $this->tempUrlGenerator->generate('GET', $storedObject->getFilename());
$response = $this->httpClient->sendRequest($this->psr17->createRequest('GET', $object->url));
if (200 !== $response->getStatusCode())
{
return $this
->psr17
->createResponse(500);
}
return $this
->psr17
->createResponse()
->withHeader(
'Content-Type',
'application/octet-stream',
)
->withHeader(
'Content-Disposition',
sprintf('attachment; filename=%s', $storedObject->getFilename())
)
->withBody($response->getBody());
return $this->wopi->getFile($fileId, $accessToken, $request);
}
public function getLock(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
{
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->getLock($fileId, $accessToken, $request);
}
public function getShareUrl(
@ -174,7 +88,7 @@ final class ChillWopi implements WopiInterface
?string $accessToken,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->getShareUrl($fileId, $accessToken, $request);
}
public function lock(
@ -183,7 +97,7 @@ final class ChillWopi implements WopiInterface
string $xWopiLock,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->lock($fileId, $accessToken, $xWopiLock, $request);
}
public function putFile(
@ -193,49 +107,17 @@ final class ChillWopi implements WopiInterface
string $xWopiEditors,
RequestInterface $request
): ResponseInterface {
try {
$user = $this->userProvider->loadUserByUsername($accessToken);
} catch (UsernameNotFoundException $e) {
return $this
->psr17
->createResponse(401);
}
$storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]);
if (null === $storedObject) {
throw new Exception(sprintf('Unable to find object named %s', $fileId));
}
// TODO: Add strict typing in champs-libres/async-uploader-bundle
/** @var StdClass $object */
$object = $this->tempUrlGenerator->generate('PUT', $storedObject->getFilename());
$response = $this->httpClient->sendRequest($this->psr17->createRequest('PUT', $object->url)->withBody($request->getBody()));
if (201 !== $response->getStatusCode())
{
return $this
->psr17
->createResponse(500);
}
return $this
->psr17
->createResponse()
->withHeader('Content-Type', 'application/json')
->withAddedHeader('X-WOPI-Lock', $xWopiLock)
->withBody($this->psr17->createStream((string) json_encode([])));
return $this->wopi->putFile($fileId, $accessToken, $xWopiLock, $xWopiEditors, $request);
}
public function putRelativeFile(string $fileId, string $accessToken, ?string $suggestedTarget, ?string $relativeTarget, bool $overwriteRelativeTarget, int $size, RequestInterface $request): ResponseInterface
{
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->putRelativeFile($fileId, $accessToken, $suggestedTarget, $relativeTarget, $overwriteRelativeTarget, $size, $request);
}
public function putUserInfo(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
{
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->putUserInfo($fileId, $accessToken, $request);
}
public function refreshLock(
@ -244,7 +126,7 @@ final class ChillWopi implements WopiInterface
string $xWopiLock,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->refreshLock($fileId, $accessToken, $xWopiLock, $request);
}
public function renameFile(
@ -254,7 +136,7 @@ final class ChillWopi implements WopiInterface
string $xWopiRequestedName,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->renameFile($fileId, $accessToken, $xWopiLock, $xWopiRequestedName, $request);
}
public function unlock(
@ -263,7 +145,7 @@ final class ChillWopi implements WopiInterface
string $xWopiLock,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
return $this->wopi->unlock($fileId, $accessToken, $xWopiLock, $request);
}
public function unlockAndRelock(
@ -273,33 +155,6 @@ final class ChillWopi implements WopiInterface
string $xWopiOldLock,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
}
private function getDebugResponse(string $method, RequestInterface $request): ResponseInterface
{
$params = [];
parse_str($request->getUri()->getQuery(), $params);
$data = (string) json_encode(array_merge(
['method' => $method],
$params,
$request->getHeaders()
));
return $this
->psr17
->createResponse()
->withHeader('content', 'application/json')
->withBody($this->psr17->createStream($data));
}
private function getLockFilepath(string $fileId): string
{
return sprintf(
'%s/%s.lock',
$this->filesRepository,
$fileId
);
return $this->wopi->unlockAndRelock($fileId, $accessToken, $xWopiLock, $xWopiOldLock, $request);
}
}