2021-08-20 10:05:54 +02:00

348 lines
11 KiB
PHP

<?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\Discovery\WopiDiscoveryInterface;
use ChampsLibres\WopiLib\WopiInterface;
use Chill\DocStoreBundle\Repository\StoredObjectRepository;
use Exception;
use loophp\psr17\Psr17Interface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
final class ChillWopi implements WopiInterface
{
private Psr17Interface $psr17;
private WopiDiscoveryInterface $wopiDiscovery;
private StoredObjectRepository $storedObjectRepository;
private ClientInterface $httpClient;
private TempUrlGeneratorInterface $tempUrlGeneratorInterface;
private Security $security;
private UserProviderInterface $userProvider;
public function __construct(
Psr17Interface $psr17,
WopiDiscoveryInterface $wopiDiscovery,
StoredObjectRepository $storedObjectRepository,
ClientInterface $httpClient,
TempUrlGeneratorInterface $tempUrlGeneratorInterface,
Security $security,
UserProviderInterface $userProvider
) {
$this->psr17 = $psr17;
$this->wopiDiscovery = $wopiDiscovery;
$this->storedObjectRepository = $storedObjectRepository;
$this->httpClient = $httpClient;
$this->tempUrlGeneratorInterface = $tempUrlGeneratorInterface;
$this->security = $security;
$this->userProvider = $userProvider;
}
public function checkFileInfo(
string $fileId,
?string $accessToken,
RequestInterface $request
): ResponseInterface {
$user = $this->userProvider->loadUserByUsername($accessToken);
$storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]);
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' => $user->getUsername(),
'Size' => 0,
'UserId' => $user->getUsername(),
// 'Version' => 'v' . uniqid(),
'ReadOnly' => false,
'UserCanWrite' => true,
'UserCanNotWriteRelative' => false,
'SupportsLocks' => true,
'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' => true,
'SupportsUpdate' => true,
'SupportsRename' => true,
'DisablePrint' => false,
'DisableExport' => false,
'DisableCopy' => false,
]
)));
}
public function deleteFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
{
return $this->getDebugResponse(__FUNCTION__, $request);
}
public function enumerateAncestors(
string $fileId,
?string $accessToken,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
}
public function getFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
{
$storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]);
// TODO: Add strict typing in champs-libres/async-uploader-bundle
/** @var StdClass $object */
$object = $this->tempUrlGeneratorInterface->generate('GET', $storedObject->getFilename());
$response = $this->httpClient->sendRequest($this->psr17->createRequest('GET', $object->url));
return $this
->psr17
->createResponse()
->withHeader(
'Content-Type',
'application/octet-stream',
)
->withHeader(
'Content-Disposition',
sprintf('attachment; filename=%s', $storedObject->getFilename())
)
->withBody($response->getBody());
}
public function getLock(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
{
$lockFilepath = $this->getLockFilepath($fileId);
$lock = '';
if ($this->fs->exists($lockFilepath)) {
$lockData = json_decode(file_get_contents($lockFilepath));
$lock = $lockData->lock;
}
return $this
->psr17
->createResponse()
->withHeader('X-WOPI-Lock', $lock);
}
public function getShareUrl(
string $fileId,
?string $accessToken,
RequestInterface $request
): ResponseInterface {
$data = [
'ShareUrl' => 'TODO',
];
return $this
->psr17
->createResponse()
->withHeader('Content-Type', 'application/json')
->withBody($this->psr17->createStream((string) json_encode($data)));
}
public function lock(
string $fileId,
?string $accessToken,
string $xWopiLock,
RequestInterface $request
): ResponseInterface {
$lockFilepath = $this->getLockFilepath($fileId);
if ($this->fs->exists($lockFilepath)) {
$previousLockData = json_decode(file_get_contents($lockFilepath));
if ($previousLockData->lock !== $xWopiLock) {
return $this
->psr17
->createResponse(409)
->withAddedHeader('X-WOPI-Lock', $previousLockData->lock);
}
}
$data = [
'lock' => $xWopiLock,
'time' => microtime(),
];
$this->fs->dumpFile($lockFilepath, json_encode($data));
return $this
->psr17
->createResponse();
}
public function putFile(
string $fileId,
?string $accessToken,
string $xWopiLock,
string $xWopiEditors,
RequestInterface $request
): ResponseInterface {
$storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]);
// TODO: Add strict typing in champs-libres/async-uploader-bundle
/** @var StdClass $object */
$object = $this->tempUrlGeneratorInterface->generate('PUT', $storedObject->getFilename());
$response = $this->httpClient->sendRequest($this->psr17->createRequest('PUT', $object->url)->withBody($request->getBody()));
if (200 !== $response->getStatusCode())
{
throw new Exception('Error in response.');
}
return $this
->psr17
->createResponse()
->withHeader('Content-Type', 'application/json')
->withAddedHeader('X-WOPI-Lock', $xWopiLock)
->withBody($this->psr17->createStream((string) json_encode([])));
}
public function putRelativeFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
{
$filepath = sprintf(
'%s/%s',
$this->filesRepository,
$request->getHeaderLine('X-WOPI-SuggestedTarget')
);
$return = file_put_contents(
$filepath,
(string) $request->getBody()
);
if (false === $return) {
return $this
->psr17
->createResponse(500);
}
return $this
->psr17
->createResponse(500); // Not supported yet.
}
public function putUserInfo(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface
{
return $this->getDebugResponse(__FUNCTION__, $request);
}
public function refreshLock(
string $fileId,
?string $accessToken,
string $xWopiLock,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
}
public function renameFile(
string $fileId,
?string $accessToken,
string $xWopiLock,
string $xWopiRequestedName,
RequestInterface $request
): ResponseInterface {
return $this->getDebugResponse(__FUNCTION__, $request);
}
public function unlock(
string $fileId,
?string $accessToken,
string $xWopiLock,
RequestInterface $request
): ResponseInterface {
$lockFilepath = $this->getLockFilepath($fileId);
if ($this->fs->exists($lockFilepath)) {
$previousLockData = json_decode(file_get_contents($lockFilepath));
if ($previousLockData->lock !== $xWopiLock) {
return $this
->psr17
->createResponse(409)
->withAddedHeader('X-WOPI-Lock', $previousLockData->lock);
}
}
$this->fs->remove($lockFilepath);
return $this
->psr17
->createResponse()
->withAddedHeader('X-WOPI-Lock', '');
}
public function unlockAndRelock(
string $fileId,
?string $accessToken,
string $xWopiLock,
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
);
}
}