Move to dedicated file attachment service
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
@@ -62,8 +62,6 @@ class AttachmentService {
|
|||||||
private $activityManager;
|
private $activityManager;
|
||||||
/** @var ChangeHelper */
|
/** @var ChangeHelper */
|
||||||
private $changeHelper;
|
private $changeHelper;
|
||||||
/** @var DeckShareProvider */
|
|
||||||
private $shareProvider;
|
|
||||||
|
|
||||||
public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, ChangeHelper $changeHelper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId, IL10N $l10n, ActivityManager $activityManager, DeckShareProvider $shareProvider) {
|
public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, ChangeHelper $changeHelper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId, IL10N $l10n, ActivityManager $activityManager, DeckShareProvider $shareProvider) {
|
||||||
$this->attachmentMapper = $attachmentMapper;
|
$this->attachmentMapper = $attachmentMapper;
|
||||||
@@ -75,11 +73,11 @@ class AttachmentService {
|
|||||||
$this->l10n = $l10n;
|
$this->l10n = $l10n;
|
||||||
$this->activityManager = $activityManager;
|
$this->activityManager = $activityManager;
|
||||||
$this->changeHelper = $changeHelper;
|
$this->changeHelper = $changeHelper;
|
||||||
$this->shareProvider = $shareProvider;
|
|
||||||
|
|
||||||
// Register shipped attachment services
|
// Register shipped attachment services
|
||||||
// TODO: move this to a plugin based approach once we have different types of attachments
|
// TODO: move this to a plugin based approach once we have different types of attachments
|
||||||
$this->registerAttachmentService('deck_file', FileService::class);
|
$this->registerAttachmentService('deck_file', FileService::class);
|
||||||
|
$this->registerAttachmentService('file', FilesAppService::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,6 +118,15 @@ class AttachmentService {
|
|||||||
if ($withDeleted) {
|
if ($withDeleted) {
|
||||||
$attachments = array_merge($attachments, $this->attachmentMapper->findToDelete($cardId, false));
|
$attachments = array_merge($attachments, $this->attachmentMapper->findToDelete($cardId, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (array_keys($this->services) as $attachmentType) {
|
||||||
|
/** @var IAttachmentService $service */
|
||||||
|
$service = $this->getService($attachmentType);
|
||||||
|
if ($service instanceof ICustomAttachmentService) {
|
||||||
|
$attachments = array_merge($attachments, $service->listAttachments($cardId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($attachments as &$attachment) {
|
foreach ($attachments as &$attachment) {
|
||||||
try {
|
try {
|
||||||
$service = $this->getService($attachment->getType());
|
$service = $this->getService($attachment->getType());
|
||||||
@@ -129,37 +136,7 @@ class AttachmentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_merge($attachments, $this->getFilesAppAttachments($cardId));
|
return $attachments;
|
||||||
}
|
|
||||||
|
|
||||||
private function getFilesAppAttachments($cardId) {
|
|
||||||
/** @var IPreview $previewManager */
|
|
||||||
$previewManager = \OC::$server->get(IPreview::class);
|
|
||||||
$userFolder = \OC::$server->getRootFolder()->getUserFolder($this->userId);
|
|
||||||
$shares = $this->shareProvider->getSharedWithByType($cardId, IShare::TYPE_DECK, -1, 0);
|
|
||||||
return array_map(function (IShare $share) use ($cardId, $userFolder, $previewManager) {
|
|
||||||
$file = $share->getNode();
|
|
||||||
$nodes = $userFolder->getById($file->getId());
|
|
||||||
$userNode = array_shift($nodes);
|
|
||||||
return [
|
|
||||||
// general attachment attributes
|
|
||||||
'cardId' => $cardId,
|
|
||||||
'type' => 'file',
|
|
||||||
'data' => $file->getName(),
|
|
||||||
'lastModified' => $file->getMTime(),
|
|
||||||
'createdAt' => $file->getMTime(),
|
|
||||||
'deletedAt' => 0,
|
|
||||||
// file type attributes
|
|
||||||
'fileid' => $file->getId(),
|
|
||||||
'path' => $userFolder->getRelativePath($userNode->getPath()),
|
|
||||||
'extendedData' => [
|
|
||||||
'filesize' => $file->getSize(),
|
|
||||||
'mimetype' => $file->getMimeType(),
|
|
||||||
'info' => pathinfo($file->getName()),
|
|
||||||
'hasPreview' => $previewManager->isAvailable($file),
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}, $shares);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -178,22 +155,7 @@ class AttachmentService {
|
|||||||
$this->cache->set('card-' . $cardId, $count);
|
$this->cache->set('card-' . $cardId, $count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var IDBConnection $qb */
|
|
||||||
$db = \OC::$server->getDatabaseConnection();
|
|
||||||
$qb = $db->getQueryBuilder();
|
|
||||||
$qb->select($qb->createFunction('count(s.id)'))
|
|
||||||
->from('share', 's')
|
|
||||||
->andWhere($qb->expr()->eq('s.share_type', $qb->createNamedParameter(IShare::TYPE_DECK)))
|
|
||||||
->andWhere($qb->expr()->eq('s.share_with', $qb->createNamedParameter($cardId)))
|
|
||||||
->andWhere($qb->expr()->isNull('s.parent'))
|
|
||||||
->andWhere($qb->expr()->orX(
|
|
||||||
$qb->expr()->eq('s.item_type', $qb->createNamedParameter('file')),
|
|
||||||
$qb->expr()->eq('s.item_type', $qb->createNamedParameter('folder'))
|
|
||||||
));
|
|
||||||
|
|
||||||
$cursor = $qb->execute();
|
|
||||||
$count += $cursor->fetchColumn(0);
|
|
||||||
$cursor->closeCursor();
|
|
||||||
|
|
||||||
return $count;
|
return $count;
|
||||||
}
|
}
|
||||||
@@ -234,21 +196,21 @@ class AttachmentService {
|
|||||||
try {
|
try {
|
||||||
$service = $this->getService($attachment->getType());
|
$service = $this->getService($attachment->getType());
|
||||||
$service->create($attachment);
|
$service->create($attachment);
|
||||||
} catch (InvalidAttachmentType $e) {
|
|
||||||
// just store the data
|
if (!$service instanceof ICustomAttachmentService) {
|
||||||
}
|
|
||||||
if ($attachment->getData() === null) {
|
if ($attachment->getData() === null) {
|
||||||
throw new StatusException($this->l10n->t('No data was provided to create an attachment.'));
|
throw new StatusException($this->l10n->t('No data was provided to create an attachment.'));
|
||||||
}
|
}
|
||||||
$attachment = $this->attachmentMapper->insert($attachment);
|
|
||||||
|
|
||||||
// extend data so the frontend can use it properly after creating
|
$attachment = $this->attachmentMapper->insert($attachment);
|
||||||
try {
|
}
|
||||||
$service = $this->getService($attachment->getType());
|
|
||||||
$service->extendData($attachment);
|
$service->extendData($attachment);
|
||||||
|
|
||||||
} catch (InvalidAttachmentType $e) {
|
} catch (InvalidAttachmentType $e) {
|
||||||
// just store the data
|
// just store the data
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_CREATE);
|
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_CREATE);
|
||||||
return $attachment;
|
return $attachment;
|
||||||
@@ -260,17 +222,11 @@ class AttachmentService {
|
|||||||
*
|
*
|
||||||
* @param $attachmentId
|
* @param $attachmentId
|
||||||
* @return Response
|
* @return Response
|
||||||
* @throws BadRequestException
|
|
||||||
* @throws NoPermissionException
|
* @throws NoPermissionException
|
||||||
* @throws NotFoundException
|
* @throws NotFoundException
|
||||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
|
||||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
|
||||||
*/
|
*/
|
||||||
public function display($attachmentId) {
|
public function display($attachmentId) {
|
||||||
if (is_numeric($attachmentId) === false) {
|
if (is_numeric($attachmentId)) {
|
||||||
throw new BadRequestException('attachment id must be a number');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$attachment = $this->attachmentMapper->find($attachmentId);
|
$attachment = $this->attachmentMapper->find($attachmentId);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -286,16 +242,28 @@ class AttachmentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$type, $attachmentId] = explode(':', $attachmentId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$attachment = new Attachment();
|
||||||
|
$attachment->setId($attachmentId);
|
||||||
|
$attachment->setType($type);
|
||||||
|
$service = $this->getService($type);
|
||||||
|
return $service->display($attachment);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an attachment with custom data
|
* Update an attachment with custom data
|
||||||
*
|
*
|
||||||
* @param $attachmentId
|
* @param $attachmentId
|
||||||
* @param $request
|
* @param $data
|
||||||
* @return mixed
|
* @return mixed
|
||||||
* @throws \OCA\Deck\NoPermissionException
|
|
||||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
|
||||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
|
||||||
* @throws BadRequestException
|
* @throws BadRequestException
|
||||||
|
* @throws NoPermissionException
|
||||||
*/
|
*/
|
||||||
public function update($attachmentId, $data) {
|
public function update($attachmentId, $data) {
|
||||||
if (is_numeric($attachmentId) === false) {
|
if (is_numeric($attachmentId) === false) {
|
||||||
|
|||||||
@@ -148,4 +148,8 @@ class ConfigService {
|
|||||||
}, $groups);
|
}, $groups);
|
||||||
return array_filter($groups);
|
return array_filter($groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getAttachmentFolder(): string {
|
||||||
|
return $this->config->getUserValue($this->userId, 'deck', 'attachment_folder', '/Deck');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ use OCP\IConfig;
|
|||||||
use OCP\IL10N;
|
use OCP\IL10N;
|
||||||
use OCP\ILogger;
|
use OCP\ILogger;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
|
use OCP\Share\IManager;
|
||||||
|
|
||||||
class FileService implements IAttachmentService {
|
class FileService implements IAttachmentService {
|
||||||
private $l10n;
|
private $l10n;
|
||||||
@@ -57,7 +58,8 @@ class FileService implements IAttachmentService {
|
|||||||
ILogger $logger,
|
ILogger $logger,
|
||||||
IRootFolder $rootFolder,
|
IRootFolder $rootFolder,
|
||||||
IConfig $config,
|
IConfig $config,
|
||||||
AttachmentMapper $attachmentMapper
|
AttachmentMapper $attachmentMapper,
|
||||||
|
IManager $shareManager
|
||||||
) {
|
) {
|
||||||
$this->l10n = $l10n;
|
$this->l10n = $l10n;
|
||||||
$this->appData = $appData;
|
$this->appData = $appData;
|
||||||
@@ -66,6 +68,7 @@ class FileService implements IAttachmentService {
|
|||||||
$this->rootFolder = $rootFolder;
|
$this->rootFolder = $rootFolder;
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->attachmentMapper = $attachmentMapper;
|
$this->attachmentMapper = $attachmentMapper;
|
||||||
|
$this->shareManager = $shareManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
260
lib/Service/FilesAppService.php
Normal file
260
lib/Service/FilesAppService.php
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Deck\Service;
|
||||||
|
|
||||||
|
use OCA\Deck\Db\Attachment;
|
||||||
|
use OCA\Deck\Db\AttachmentMapper;
|
||||||
|
use OCA\Deck\Sharing\DeckShareProvider;
|
||||||
|
use OCA\Deck\StatusException;
|
||||||
|
use OCA\Deck\Exceptions\ConflictException;
|
||||||
|
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||||
|
use OCP\AppFramework\Http\FileDisplayResponse;
|
||||||
|
use OCP\AppFramework\Http\Response;
|
||||||
|
use OCP\AppFramework\Http\StreamResponse;
|
||||||
|
use OCP\Constants;
|
||||||
|
use OCP\Files\IAppData;
|
||||||
|
use OCP\Files\IRootFolder;
|
||||||
|
use OCP\Files\NotFoundException;
|
||||||
|
use OCP\Files\NotPermittedException;
|
||||||
|
use OCP\Files\SimpleFS\ISimpleFile;
|
||||||
|
use OCP\Files\SimpleFS\ISimpleFolder;
|
||||||
|
use OCP\IConfig;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
use OCP\IL10N;
|
||||||
|
use OCP\ILogger;
|
||||||
|
use OCP\IPreview;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Share;
|
||||||
|
use OCP\Share\IManager;
|
||||||
|
use OCP\Share\IShare;
|
||||||
|
|
||||||
|
class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
||||||
|
|
||||||
|
private $request;
|
||||||
|
private $rootFolder;
|
||||||
|
private $shareProvider;
|
||||||
|
private $shareManager;
|
||||||
|
private $userId;
|
||||||
|
private $configService;
|
||||||
|
private $l10n;
|
||||||
|
private $preview;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
IRequest $request,
|
||||||
|
IL10N $l10n,
|
||||||
|
IRootFolder $rootFolder,
|
||||||
|
IManager $shareManager,
|
||||||
|
ConfigService $configService,
|
||||||
|
DeckShareProvider $shareProvider,
|
||||||
|
IPreview $preview,
|
||||||
|
string $userId = null
|
||||||
|
) {
|
||||||
|
$this->request = $request;
|
||||||
|
$this->l10n = $l10n;
|
||||||
|
$this->rootFolder = $rootFolder;
|
||||||
|
$this->configService = $configService;
|
||||||
|
$this->shareProvider = $shareProvider;
|
||||||
|
$this->shareManager = $shareManager;
|
||||||
|
$this->userId = $userId;
|
||||||
|
$this->preview = $preview;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listAttachments(int $cardId): array {
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||||
|
$shares = $this->shareProvider->getSharedWithByType($cardId, IShare::TYPE_DECK, -1, 0);
|
||||||
|
return array_map(function (IShare $share) use ($cardId, $userFolder) {
|
||||||
|
$file = $share->getNode();
|
||||||
|
$nodes = $userFolder->getById($file->getId());
|
||||||
|
$file = array_shift($nodes);
|
||||||
|
$attachment = new Attachment();
|
||||||
|
$attachment->setType('file');
|
||||||
|
$attachment->setId($file->getId());
|
||||||
|
$attachment->setCardId($cardId);
|
||||||
|
$attachment->setCreatedBy($share->getSharedBy());
|
||||||
|
$attachment->setData($file->getName());
|
||||||
|
$attachment->setLastModified($file->getMTime());
|
||||||
|
$attachment->setCreatedAt($share->getShareTime()->getTimestamp());
|
||||||
|
$attachment->setDeletedAt(0);
|
||||||
|
return $attachment;
|
||||||
|
}, $shares);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAttachmentCount(int $cardId): int {
|
||||||
|
/** @var IDBConnection $qb */
|
||||||
|
$db = \OC::$server->getDatabaseConnection();
|
||||||
|
$qb = $db->getQueryBuilder();
|
||||||
|
$qb->select($qb->createFunction('count(s.id)'))
|
||||||
|
->from('share', 's')
|
||||||
|
->andWhere($qb->expr()->eq('s.share_type', $qb->createNamedParameter(IShare::TYPE_DECK)))
|
||||||
|
->andWhere($qb->expr()->eq('s.share_with', $qb->createNamedParameter($cardId)))
|
||||||
|
->andWhere($qb->expr()->isNull('s.parent'))
|
||||||
|
->andWhere($qb->expr()->orX(
|
||||||
|
$qb->expr()->eq('s.item_type', $qb->createNamedParameter('file')),
|
||||||
|
$qb->expr()->eq('s.item_type', $qb->createNamedParameter('folder'))
|
||||||
|
));
|
||||||
|
|
||||||
|
$cursor = $qb->execute();
|
||||||
|
$count = $cursor->fetchColumn(0);
|
||||||
|
$cursor->closeCursor();
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function extendData(Attachment $attachment) {
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||||
|
$nodes = $userFolder->getById($attachment->getId());
|
||||||
|
$file = array_shift($nodes);
|
||||||
|
$attachment->setExtendedData([
|
||||||
|
'path' => $userFolder->getRelativePath($file->getPath()),
|
||||||
|
'fileid' => $file->getId(),
|
||||||
|
'data' => $file->getName(),
|
||||||
|
'filesize' => $file->getSize(),
|
||||||
|
'mimetype' => $file->getMimeType(),
|
||||||
|
'info' => pathinfo($file->getName()),
|
||||||
|
'hasPreview' => $this->preview->isAvailable($file),
|
||||||
|
]);
|
||||||
|
return $attachment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function display(Attachment $attachment) {
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||||
|
$nodes = $userFolder->getById($attachment->getId());
|
||||||
|
$file = array_shift($nodes);
|
||||||
|
if ($file === null) {
|
||||||
|
throw new NotFoundException('File not found');
|
||||||
|
}
|
||||||
|
if (method_exists($file, 'fopen')) {
|
||||||
|
$response = new StreamResponse($file->fopen('r'));
|
||||||
|
$response->addHeader('Content-Disposition', 'inline; filename="' . rawurldecode($file->getName()) . '"');
|
||||||
|
} else {
|
||||||
|
$response = new FileDisplayResponse($file);
|
||||||
|
}
|
||||||
|
// We need those since otherwise chrome won't show the PDF file with CSP rule object-src 'none'
|
||||||
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=271452
|
||||||
|
$policy = new ContentSecurityPolicy();
|
||||||
|
$policy->addAllowedObjectDomain('\'self\'');
|
||||||
|
$policy->addAllowedObjectDomain('blob:');
|
||||||
|
$policy->addAllowedMediaDomain('\'self\'');
|
||||||
|
$policy->addAllowedMediaDomain('blob:');
|
||||||
|
$response->setContentSecurityPolicy($policy);
|
||||||
|
|
||||||
|
$response->addHeader('Content-Type', $file->getMimeType());
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(Attachment $attachment) {
|
||||||
|
$file = $this->getUploadedFile();
|
||||||
|
$fileName = $file['name'];
|
||||||
|
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||||
|
$folder = $userFolder->get($this->configService->getAttachmentFolder());
|
||||||
|
|
||||||
|
// FIXME: Add to docs that conflict handling is different here, no ConflictException will be thrown
|
||||||
|
$fileName = $folder->getNonExistingName($fileName);
|
||||||
|
$target = $folder->newFile($fileName);
|
||||||
|
$content = fopen($file['tmp_name'], 'rb');
|
||||||
|
if ($content === false) {
|
||||||
|
throw new StatusException('Could not read file');
|
||||||
|
}
|
||||||
|
$target->putContent($content);
|
||||||
|
if (is_resource($content)) {
|
||||||
|
fclose($content);
|
||||||
|
}
|
||||||
|
|
||||||
|
$share = $this->shareManager->newShare();
|
||||||
|
$share->setNode($target);
|
||||||
|
$share->setShareType(Share::SHARE_TYPE_DECK);
|
||||||
|
$share->setSharedWith((string)$attachment->getCardId());
|
||||||
|
$share->setPermissions(Constants::PERMISSION_READ);
|
||||||
|
$share->setSharedBy($this->userId);
|
||||||
|
$this->shareManager->createShare($share);
|
||||||
|
$attachment->setId($target->getId());
|
||||||
|
$attachment->setData($target->getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
* @throws StatusException
|
||||||
|
*/
|
||||||
|
private function getUploadedFile() {
|
||||||
|
$file = $this->request->getUploadedFile('file');
|
||||||
|
$error = null;
|
||||||
|
$phpFileUploadErrors = [
|
||||||
|
UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
|
||||||
|
UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
|
||||||
|
UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
|
||||||
|
UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
|
||||||
|
UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
|
||||||
|
UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
|
||||||
|
UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
|
||||||
|
UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (empty($file)) {
|
||||||
|
$error = $this->l10n->t('No file uploaded or file size exceeds maximum of %s', [\OCP\Util::humanFileSize(\OCP\Util::uploadLimit())]);
|
||||||
|
}
|
||||||
|
if (!empty($file) && array_key_exists('error', $file) && $file['error'] !== UPLOAD_ERR_OK) {
|
||||||
|
$error = $phpFileUploadErrors[$file['error']];
|
||||||
|
}
|
||||||
|
if ($error !== null) {
|
||||||
|
throw new StatusException($error);
|
||||||
|
}
|
||||||
|
return $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Attachment $attachment) {
|
||||||
|
// TODO: Implement update() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Attachment $attachment) {
|
||||||
|
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||||
|
$nodes = $userFolder->getById($attachment->getId());
|
||||||
|
$file = array_shift($nodes);
|
||||||
|
if ($file === null) {
|
||||||
|
throw new NotFoundException('File not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($file->getOwner() !== null && $file->getOwner()->getUID() === $this->userId) {
|
||||||
|
$file->delete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: only with manage permissions
|
||||||
|
$shares = $this->shareProvider->getSharedWithByType($attachment->getCardId(), IShare::TYPE_DECK, -1, 0);
|
||||||
|
foreach ($shares as $share) {
|
||||||
|
if ($share->getNode()->getId() === $attachment->getId()) {
|
||||||
|
$this->shareManager->deleteShare($share);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function allowUndo() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markAsDeleted(Attachment $attachment) {
|
||||||
|
throw new \Exception('Not implemented');
|
||||||
|
}
|
||||||
|
}
|
||||||
42
lib/Service/ICustomAttachmentService.php
Normal file
42
lib/Service/ICustomAttachmentService.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
|
||||||
|
namespace OCA\Deck\Service;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to implement in case attachments are handled by a different backend than
|
||||||
|
* then oc_deck_attachments table, e.g. for file sharing. When this interface is used
|
||||||
|
* for implementing an attachment handler no backlink will be stored in the deck attachments
|
||||||
|
* table and it is up to the implementation to track attachment to card relation.
|
||||||
|
*/
|
||||||
|
interface ICustomAttachmentService {
|
||||||
|
|
||||||
|
public function listAttachments(int $cardId): array;
|
||||||
|
|
||||||
|
public function getAttachmentCount(int $cardId): int;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ namespace OCA\Deck\Sharing;
|
|||||||
|
|
||||||
|
|
||||||
use OC\Files\Filesystem;
|
use OC\Files\Filesystem;
|
||||||
|
use OCA\Deck\Service\ConfigService;
|
||||||
use OCP\EventDispatcher\IEventDispatcher;
|
use OCP\EventDispatcher\IEventDispatcher;
|
||||||
use OCP\Share\Events\VerifyMountPointEvent;
|
use OCP\Share\Events\VerifyMountPointEvent;
|
||||||
use OCP\Share\IShare;
|
use OCP\Share\IShare;
|
||||||
@@ -35,7 +36,13 @@ use Symfony\Component\EventDispatcher\GenericEvent;
|
|||||||
|
|
||||||
class Listener {
|
class Listener {
|
||||||
|
|
||||||
public function __construct($userId) {
|
/** @var ConfigService */
|
||||||
|
private $configService;
|
||||||
|
/** @var string|null */
|
||||||
|
private $userId;
|
||||||
|
|
||||||
|
public function __construct(ConfigService $configService, $userId) {
|
||||||
|
$this->configService = $configService;
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,15 +106,11 @@ class Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$parent = $this->getAttachmentFolder();
|
$parent = $this->configService->getAttachmentFolder();
|
||||||
$event->setParent($parent);
|
$event->setParent($parent);
|
||||||
if (!$event->getView()->is_dir($parent)) {
|
if (!$event->getView()->is_dir($parent)) {
|
||||||
$event->getView()->mkdir($parent);
|
$event->getView()->mkdir($parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getAttachmentFolder() {
|
|
||||||
return \OC::$server->getConfig()->getUserValue($this->userId, 'deck', 'attachment_folder', '/Deck');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,17 +69,17 @@
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Actions>
|
</Actions>
|
||||||
<Actions v-if="removable" :force-menu="true">
|
<Actions v-if="removable" :force-menu="true">
|
||||||
<ActionLink v-if="attachment.fileid" icon="icon-folder" :href="'/index.php/f/'+attachment.fileid">
|
<ActionLink v-if="attachment.extendedData.fileid" icon="icon-folder" :href="'/index.php/f/'+attachment.extendedData.fileid">
|
||||||
{{ t('deck', 'Show in files') }}
|
{{ t('deck', 'Show in files') }}
|
||||||
</ActionLink>
|
</ActionLink>
|
||||||
<ActionButton v-if="attachment.fileid" icon="icon-delete">
|
<ActionButton v-if="attachment.extendedData.fileid" icon="icon-delete">
|
||||||
{{ t('deck', 'Unshare file') }}
|
{{ t('deck', 'Unshare file') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|
||||||
<ActionButton v-if="!attachment.fileid && attachment.deletedAt === 0" icon="icon-delete" @click="$emit('deleteAttachment', attachment)">
|
<ActionButton v-if="!attachment.extendedData.fileid && attachment.deletedAt === 0" icon="icon-delete" @click="$emit('deleteAttachment', attachment)">
|
||||||
{{ t('deck', 'Delete Attachment') }}
|
{{ t('deck', 'Delete Attachment') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton v-else-if="!attachment.fileid" icon="icon-history" @click="$emit('restoreAttachment', attachment)">
|
<ActionButton v-else-if="!attachment.extendedData.fileid" icon="icon-history" @click="$emit('restoreAttachment', attachment)">
|
||||||
{{ t('deck', 'Restore Attachment') }}
|
{{ t('deck', 'Restore Attachment') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Actions>
|
</Actions>
|
||||||
@@ -89,11 +89,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import axios from '@nextcloud/axios'
|
||||||
import { Actions, ActionButton, ActionLink } from '@nextcloud/vue'
|
import { Actions, ActionButton, ActionLink } from '@nextcloud/vue'
|
||||||
import AttachmentDragAndDrop from '../AttachmentDragAndDrop'
|
import AttachmentDragAndDrop from '../AttachmentDragAndDrop'
|
||||||
import relativeDate from '../../mixins/relativeDate'
|
import relativeDate from '../../mixins/relativeDate'
|
||||||
import { formatFileSize } from '@nextcloud/files'
|
import { formatFileSize } from '@nextcloud/files'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl, generateOcsUrl } from '@nextcloud/router'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import { loadState } from '@nextcloud/initial-state'
|
import { loadState } from '@nextcloud/initial-state'
|
||||||
import attachmentUpload from '../../mixins/attachmentUpload'
|
import attachmentUpload from '../../mixins/attachmentUpload'
|
||||||
@@ -154,7 +155,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
attachmentPreview() {
|
attachmentPreview() {
|
||||||
return (attachment) => (attachment.fileid ? generateUrl(`/core/preview?fileId=${attachment.fileid}&x=64&y=64&a=true`) : null)
|
return (attachment) => (attachment.extendedData.fileid ? generateUrl(`/core/preview?fileId=${attachment.extendedData.fileid}&x=64&y=64&a=true`) : null)
|
||||||
},
|
},
|
||||||
attachmentUrl() {
|
attachmentUrl() {
|
||||||
return (attachment) => generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`)
|
return (attachment) => generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`)
|
||||||
@@ -202,7 +203,15 @@ export default {
|
|||||||
if (!path.startsWith('/')) {
|
if (!path.startsWith('/')) {
|
||||||
throw new Error(t('files', 'Invalid path selected'))
|
throw new Error(t('files', 'Invalid path selected'))
|
||||||
}
|
}
|
||||||
// FIXME: Share file
|
|
||||||
|
axios.post(generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares', {
|
||||||
|
path,
|
||||||
|
permissions: 19,
|
||||||
|
shareType: 12,
|
||||||
|
shareWith: '' + this.cardId,
|
||||||
|
}).then(() => {
|
||||||
|
this.$store.dispatch('fetchAttachments', this.cardId)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
clickAddNewAttachmment() {
|
clickAddNewAttachmment() {
|
||||||
@@ -210,10 +219,10 @@ export default {
|
|||||||
},
|
},
|
||||||
showViewer(attachment) {
|
showViewer(attachment) {
|
||||||
if (window.OCA.Viewer.availableHandlers.map(handler => handler.mimes).flat().includes(attachment.extendedData.mimetype)) {
|
if (window.OCA.Viewer.availableHandlers.map(handler => handler.mimes).flat().includes(attachment.extendedData.mimetype)) {
|
||||||
window.OCA.Viewer.open(attachment.path)
|
window.OCA.Viewer.open(attachment.extendedData.path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
window.location = generateUrl('/f/' + attachment.fileid)
|
window.location = generateUrl('/f/' + attachment.extendedData.fileid)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export default {
|
|||||||
this.$set(this.uploadQueue, file.name, { name: file.name, progress: 0 })
|
this.$set(this.uploadQueue, file.name, { name: file.name, progress: 0 })
|
||||||
const bodyFormData = new FormData()
|
const bodyFormData = new FormData()
|
||||||
bodyFormData.append('cardId', this.cardId)
|
bodyFormData.append('cardId', this.cardId)
|
||||||
bodyFormData.append('type', 'deck_file')
|
bodyFormData.append('type', 'file')
|
||||||
bodyFormData.append('file', file)
|
bodyFormData.append('file', file)
|
||||||
await queue.add(async() => {
|
await queue.add(async() => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user