Added an option to download a attachment via URL. Implemented secure attachments, which can not be accessed by URL and requires authentication.

This commit is contained in:
Jan Böhmer 2019-10-19 18:42:06 +02:00
parent d382021fee
commit 27a001b1d2
10 changed files with 266 additions and 5 deletions

View file

@ -105,7 +105,11 @@ abstract class BaseAdminController extends AbstractController
$attachments = $form['attachments'];
foreach ($attachments as $attachment) {
/** @var $attachment FormInterface */
$this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData());
$options = [
'secure_attachment' => $attachment['secureFile']->getData(),
'download_url' => $attachment['downloadURL']->getData()
];
$this->attachmentSubmitHandler->handleFormSubmit($attachment->getData(), $attachment['file']->getData(), $options);
}
$em->persist($entity);

View file

@ -291,6 +291,9 @@ abstract class Attachment extends NamedDBElement
*/
public function setFilename(?string $new_filename): Attachment
{
if ($new_filename === "") {
$new_filename = null;
}
$this->original_filename = $new_filename;
return $this;
}

View file

@ -0,0 +1,39 @@
<?php
/**
*
* part-db version 0.1
* Copyright (C) 2005 Christoph Lechner
* http://www.cl-projects.de/
*
* part-db version 0.2+
* Copyright (C) 2009 K. Jacobs and others (see authors.php)
* http://code.google.com/p/part-db/
*
* Part-DB Version 0.4+
* Copyright (C) 2016 - 2019 Jan Böhmer
* https://github.com/jbtronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
*/
namespace App\Exceptions;
use Throwable;
class AttachmentDownloadException extends \RuntimeException
{
}

View file

@ -104,6 +104,12 @@ class AttachmentFormType extends AbstractType
'attr' => ['class' => 'form-control-sm'],
'label_attr' => ['class' => 'checkbox-custom']]);
$builder->add('secureFile', CheckboxType::class, ['required' => false,
'label' => $this->trans->trans('attachment.edit.secure_file'),
'mapped' => false,
'attr' => ['class' => 'form-control-sm'],
'label_attr' => ['class' => 'checkbox-custom']]);
$builder->add('file', FileType::class, [
'label' => $this->trans->trans('attachment.edit.file'),
'mapped' => false,

View file

@ -46,10 +46,19 @@ use App\Entity\Attachments\PartAttachment;
use App\Entity\Attachments\StorelocationAttachment;
use App\Entity\Attachments\SupplierAttachment;
use App\Entity\Attachments\UserAttachment;
use App\Exceptions\AttachmentDownloadException;
use App\Services\AttachmentHelper;
use Doctrine\Common\Annotations\IndexedReader;
use Nyholm\Psr7\Request;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Mime\MimeTypeGuesserInterface;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Mime\MimeTypesInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* This service handles the form submitting of an attachment and handles things like file uploading and downloading.
@ -60,11 +69,16 @@ class AttachmentSubmitHandler
protected $pathResolver;
protected $folder_mapping;
protected $allow_attachments_downloads;
protected $httpClient;
protected $mimeTypes;
public function __construct(AttachmentPathResolver $pathResolver, bool $allow_attachments_downloads)
public function __construct(AttachmentPathResolver $pathResolver, bool $allow_attachments_downloads,
HttpClientInterface $httpClient, MimeTypesInterface $mimeTypes)
{
$this->pathResolver = $pathResolver;
$this->allow_attachments_downloads = $allow_attachments_downloads;
$this->httpClient = $httpClient;
$this->mimeTypes = $mimeTypes;
//The mapping used to determine which folder will be used for an attachment type
$this->folder_mapping = [PartAttachment::class => 'part', AttachmentTypeAttachment::class => 'attachment_type',
@ -182,6 +196,7 @@ class AttachmentSubmitHandler
* @param Attachment $attachment
* @param array $options The options from the handleFormSubmit function
* @return Attachment The attachment with the new filepath
* @throws AttachmentDownloadException
*/
protected function downloadURL(Attachment $attachment, array $options) : Attachment
{
@ -189,6 +204,73 @@ class AttachmentSubmitHandler
if (!$this->allow_attachments_downloads) {
throw new \RuntimeException('Download of attachments is not allowed!');
}
$url = $attachment->getURL();
$fs = new Filesystem();
$attachment_folder = $this->generateAttachmentPath($attachment, $options['secure_attachment']);
$tmp_path = $attachment_folder . DIRECTORY_SEPARATOR . $this->generateAttachmentFilename($attachment, 'tmp');
try {
$response = $this->httpClient->request('GET', $url, [
'buffer' => false,
]);
if (200 !== $response->getStatusCode()) {
throw new AttachmentDownloadException('Statuscode:' . $response->getStatusCode());
}
//Open a temporary file in the attachment folder
$fs->mkdir($attachment_folder);
$fileHandler = fopen($tmp_path, 'wb');
//Write the downloaded data to file
foreach ($this->httpClient->stream($response) as $chunk) {
fwrite($fileHandler, $chunk->getContent());
}
fclose($fileHandler);
//File download should be finished here, so determine the new filename and extension
$headers = $response->getHeaders();
//Try to determine an filename
$filename = "";
//If an content disposition header was set try to extract the filename out of it
if (isset($headers['content-disposition'])) {
$tmp = [];
preg_match('/[^;\\n=]*=([\'\"])*(.*)(?(1)\1|)/', $headers['content-disposition'][0], $tmp);
$filename = $tmp[2];
}
//If we dont know filename yet, try to determine it out of url
if ($filename === "") {
$filename = basename(parse_url($url, PHP_URL_PATH));
}
//Set original file
$attachment->setFilename($filename);
//Check if we have a extension given
$pathinfo = pathinfo($filename);
if (!empty($pathinfo['extension'])) {
$new_ext = $pathinfo['extension'];
} else { //Otherwise we have to guess the extension for the new file, based on its content
$new_ext = $this->mimeTypes->getExtensions($this->mimeTypes->guessMimeType($tmp_path))[0] ?? 'tmp';
}
//Rename the file to its new name and save path to attachment entity
$new_path = $attachment_folder . DIRECTORY_SEPARATOR . $this->generateAttachmentFilename($attachment, $new_ext);
$fs->rename($tmp_path, $new_path);
//Make our file path relative to %BASE%
$new_path = $this->pathResolver->realPathToPlaceholder($new_path);
//Save the path to the attachment
$attachment->setPath($new_path);
} catch (TransportExceptionInterface $exception) {
throw new AttachmentDownloadException('Transport error!');
}
return $attachment;
}
/**
@ -200,7 +282,6 @@ class AttachmentSubmitHandler
*/
protected function upload(Attachment $attachment, UploadedFile $file, array $options) : Attachment
{
//Move our temporay attachment to its final location
$file_path = $file->move(
$this->generateAttachmentPath($attachment, $options['secure_attachment']),