mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-20 17:19:34 +00:00
Split attachment path into internal and external path, so the external source URL can be retained after a file is downloaded
This commit is contained in:
parent
5bb79b5419
commit
ceb7c7bd65
21 changed files with 399 additions and 303 deletions
39
migrations/Version20250112051523.php
Normal file
39
migrations/Version20250112051523.php
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php /** @noinspection SqlNoDataSourceInspection */
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250112051523 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Split $path property for attachments into $internal_path and $external_path';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{ $this->addSql('ALTER TABLE attachments ADD internal_path VARCHAR(255) DEFAULT \'\' NOT NULL');
|
||||||
|
$this->addSql('ALTER TABLE attachments RENAME COLUMN path TO external_path');
|
||||||
|
|
||||||
|
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%MEDIA#%%\' ESCAPE \'#\'');
|
||||||
|
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%BASE#%%\' ESCAPE \'#\'');
|
||||||
|
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%SECURE#%%\' ESCAPE \'#\'');
|
||||||
|
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%FOOTPRINTS#%%\' ESCAPE \'#\'');
|
||||||
|
$this->addSql('UPDATE attachments SET internal_path=external_path WHERE external_path LIKE \'#%FOOTPRINTS3D#%%\' ESCAPE \'#\'');
|
||||||
|
$this->addSql('UPDATE attachments SET external_path=\'\' WHERE internal_path <> \'\'');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('UPDATE attachments SET external_path=internal_path WHERE internal_path <> \'\'');
|
||||||
|
|
||||||
|
$this->addSql('ALTER TABLE attachments DROP internal_path');
|
||||||
|
$this->addSql('ALTER TABLE attachments RENAME COLUMN external_path TO path');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,15 +51,15 @@ class AttachmentFileController extends AbstractController
|
||||||
$this->denyAccessUnlessGranted('show_private', $attachment);
|
$this->denyAccessUnlessGranted('show_private', $attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($attachment->isExternal()) {
|
if (!$attachment->hasInternal()) {
|
||||||
throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!');
|
throw $this->createNotFoundException('The file for this attachment is external and not stored locally!');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$helper->isFileExisting($attachment)) {
|
if (!$helper->isInternalFileExisting($attachment)) {
|
||||||
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
|
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_path = $helper->toAbsoluteFilePath($attachment);
|
$file_path = $helper->toAbsoluteInternalFilePath($attachment);
|
||||||
$response = new BinaryFileResponse($file_path);
|
$response = new BinaryFileResponse($file_path);
|
||||||
|
|
||||||
//Set header content disposition, so that the file will be downloaded
|
//Set header content disposition, so that the file will be downloaded
|
||||||
|
|
@ -80,15 +80,15 @@ class AttachmentFileController extends AbstractController
|
||||||
$this->denyAccessUnlessGranted('show_private', $attachment);
|
$this->denyAccessUnlessGranted('show_private', $attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($attachment->isExternal()) {
|
if (!$attachment->hasInternal()) {
|
||||||
throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!');
|
throw $this->createNotFoundException('The file for this attachment is external and not stored locally!');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$helper->isFileExisting($attachment)) {
|
if (!$helper->isInternalFileExisting($attachment)) {
|
||||||
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
|
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_path = $helper->toAbsoluteFilePath($attachment);
|
$file_path = $helper->toAbsoluteInternalFilePath($attachment);
|
||||||
$response = new BinaryFileResponse($file_path);
|
$response = new BinaryFileResponse($file_path);
|
||||||
|
|
||||||
//Set header content disposition, so that the file will be downloaded
|
//Set header content disposition, so that the file will be downloaded
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ class PartFixtures extends Fixture implements DependentFixtureInterface
|
||||||
|
|
||||||
$attachment = new PartAttachment();
|
$attachment = new PartAttachment();
|
||||||
$attachment->setName('Test2');
|
$attachment->setName('Test2');
|
||||||
$attachment->setPath('invalid');
|
$attachment->setInternalPath('invalid');
|
||||||
$attachment->setShowInTable(true);
|
$attachment->setShowInTable(true);
|
||||||
$attachment->setAttachmentType($manager->find(AttachmentType::class, 1));
|
$attachment->setAttachmentType($manager->find(AttachmentType::class, 1));
|
||||||
$part->addAttachment($attachment);
|
$part->addAttachment($attachment);
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
||||||
{
|
{
|
||||||
$dataTable->add('dont_matter', RowClassColumn::class, [
|
$dataTable->add('dont_matter', RowClassColumn::class, [
|
||||||
'render' => function ($value, Attachment $context): string {
|
'render' => function ($value, Attachment $context): string {
|
||||||
//Mark attachments with missing files yellow
|
//Mark attachments yellow which have an internal file linked that doesn't exist
|
||||||
if(!$this->attachmentHelper->isFileExisting($context)){
|
if($context->hasInternal() && !$this->attachmentHelper->isInternalFileExisting($context)){
|
||||||
return 'table-warning';
|
return 'table-warning';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,8 +64,8 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
||||||
'className' => 'no-colvis',
|
'className' => 'no-colvis',
|
||||||
'render' => function ($value, Attachment $context): string {
|
'render' => function ($value, Attachment $context): string {
|
||||||
if ($context->isPicture()
|
if ($context->isPicture()
|
||||||
&& !$context->isExternal()
|
&& $this->attachmentHelper->isInternalFileExisting($context)) {
|
||||||
&& $this->attachmentHelper->isFileExisting($context)) {
|
|
||||||
$title = htmlspecialchars($context->getName());
|
$title = htmlspecialchars($context->getName());
|
||||||
if ($context->getFilename()) {
|
if ($context->getFilename()) {
|
||||||
$title .= ' ('.htmlspecialchars($context->getFilename()).')';
|
$title .= ' ('.htmlspecialchars($context->getFilename()).')';
|
||||||
|
|
@ -93,26 +93,6 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
||||||
$dataTable->add('name', TextColumn::class, [
|
$dataTable->add('name', TextColumn::class, [
|
||||||
'label' => 'attachment.edit.name',
|
'label' => 'attachment.edit.name',
|
||||||
'orderField' => 'NATSORT(attachment.name)',
|
'orderField' => 'NATSORT(attachment.name)',
|
||||||
'render' => function ($value, Attachment $context) {
|
|
||||||
//Link to external source
|
|
||||||
if ($context->isExternal()) {
|
|
||||||
return sprintf(
|
|
||||||
'<a href="%s" class="link-external">%s</a>',
|
|
||||||
htmlspecialchars((string) $context->getURL()),
|
|
||||||
htmlspecialchars($value)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->attachmentHelper->isFileExisting($context)) {
|
|
||||||
return sprintf(
|
|
||||||
'<a href="%s" target="_blank" data-no-ajax>%s</a>',
|
|
||||||
$this->entityURLGenerator->viewURL($context),
|
|
||||||
htmlspecialchars($value)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dataTable->add('attachment_type', TextColumn::class, [
|
$dataTable->add('attachment_type', TextColumn::class, [
|
||||||
|
|
@ -136,15 +116,42 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dataTable->add('filename', TextColumn::class, [
|
$dataTable->add('internal_link', TextColumn::class, [
|
||||||
'label' => $this->translator->trans('attachment.table.filename'),
|
'label' => 'Internal copy', #TODO: translation
|
||||||
'propertyPath' => 'filename',
|
'propertyPath' => 'filename',
|
||||||
|
'render' => function ($value, Attachment $context) {
|
||||||
|
if ($this->attachmentHelper->isInternalFileExisting($context)) {
|
||||||
|
return sprintf(
|
||||||
|
'<a href="%s" target="_blank" data-no-ajax>%s</a>',
|
||||||
|
$this->entityURLGenerator->viewURL($context),
|
||||||
|
htmlspecialchars($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dataTable->add('external_link', TextColumn::class, [
|
||||||
|
'label' => 'External copy', #TODO: translation
|
||||||
|
'propertyPath' => 'host',
|
||||||
|
'render' => function ($value, Attachment $context) {
|
||||||
|
if ($context->hasExternal()) {
|
||||||
|
return sprintf(
|
||||||
|
'<a href="%s" class="link-external">%s</a>',
|
||||||
|
htmlspecialchars((string) $context->getExternalPath()),
|
||||||
|
htmlspecialchars($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dataTable->add('filesize', TextColumn::class, [
|
$dataTable->add('filesize', TextColumn::class, [
|
||||||
'label' => $this->translator->trans('attachment.table.filesize'),
|
'label' => $this->translator->trans('attachment.table.filesize'),
|
||||||
'render' => function ($value, Attachment $context) {
|
'render' => function ($value, Attachment $context) {
|
||||||
if ($context->isExternal()) {
|
if (!$context->hasInternal()) {
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<span class="badge bg-primary">
|
'<span class="badge bg-primary">
|
||||||
<i class="fas fa-globe fa-fw"></i>%s
|
<i class="fas fa-globe fa-fw"></i>%s
|
||||||
|
|
@ -153,8 +160,13 @@ final class AttachmentDataTable implements DataTableTypeInterface
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->attachmentHelper->isFileExisting($context)) {
|
if ($this->attachmentHelper->isInternalFileExisting($context)) {
|
||||||
return $this->attachmentHelper->getHumanFileSize($context);
|
return sprintf(
|
||||||
|
'<span class="badge bg-secondary">
|
||||||
|
<i class="fas fa-hdd fa-fw"></i> %s
|
||||||
|
</span>',
|
||||||
|
$this->attachmentHelper->getHumanFileSize($context)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,16 @@ use LogicException;
|
||||||
denormalizationContext: ['groups' => ['attachment:write', 'attachment:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
denormalizationContext: ['groups' => ['attachment:write', 'attachment:write:standalone', 'api:basic:write'], 'openapi_definition_name' => 'Write'],
|
||||||
processor: HandleAttachmentsUploadsProcessor::class,
|
processor: HandleAttachmentsUploadsProcessor::class,
|
||||||
)]
|
)]
|
||||||
#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'media_url', type: 'string', nullable: true,
|
//This property is added by the denormalizer in order to resolve the placeholder
|
||||||
description: 'The URL to the file, where the attachment file can be downloaded. This can be an internal or external URL.',
|
#[DocumentedAPIProperty(
|
||||||
example: '/media/part/2/bc547-6508afa5a79c8.pdf')]
|
schemaName: 'Attachment-Read', property: 'internal_path', type: 'string', nullable: false,
|
||||||
#[DocumentedAPIProperty(schemaName: 'Attachment-Read', property: 'thumbnail_url', type: 'string', nullable: true,
|
description: 'The URL to the internally saved copy of the file, if one exists',
|
||||||
description: 'The URL to a thumbnail version of this file. This only exists for internal picture attachments.')]
|
example: '/media/part/2/bc547-6508afa5a79c8.pdf'
|
||||||
|
)]
|
||||||
|
#[DocumentedAPIProperty(
|
||||||
|
schemaName: 'Attachment-Read', property: 'thumbnail_url', type: 'string', nullable: true,
|
||||||
|
description: 'The URL to a thumbnail version of this file. This only exists for internal picture attachments.'
|
||||||
|
)]
|
||||||
#[ApiFilter(LikeFilter::class, properties: ["name"])]
|
#[ApiFilter(LikeFilter::class, properties: ["name"])]
|
||||||
#[ApiFilter(EntityFilter::class, properties: ["attachment_type"])]
|
#[ApiFilter(EntityFilter::class, properties: ["attachment_type"])]
|
||||||
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
#[ApiFilter(DateFilter::class, strategy: DateFilterInterface::EXCLUDE_NULL)]
|
||||||
|
|
@ -119,10 +124,6 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
*/
|
*/
|
||||||
final public const MODEL_EXTS = ['x3d'];
|
final public const MODEL_EXTS = ['x3d'];
|
||||||
|
|
||||||
/**
|
|
||||||
* When the path begins with one of the placeholders.
|
|
||||||
*/
|
|
||||||
final public const INTERNAL_PLACEHOLDER = ['%BASE%', '%MEDIA%', '%SECURE%'];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array placeholders for attachments which using built in files
|
* @var array placeholders for attachments which using built in files
|
||||||
|
|
@ -152,10 +153,21 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
protected ?string $original_filename = null;
|
protected ?string $original_filename = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string The path to the file relative to a placeholder path like %MEDIA%
|
* @var string If a copy of the file is stored internally, the path to the file relative to a placeholder
|
||||||
|
* path like %MEDIA%
|
||||||
*/
|
*/
|
||||||
#[ORM\Column(name: 'path', type: Types::STRING)]
|
#[ORM\Column(type: Types::STRING)]
|
||||||
protected string $path = '';
|
protected string $internal_path = '';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The path to the external source if the file is stored externally or was downloaded from an
|
||||||
|
* external source
|
||||||
|
*/
|
||||||
|
#[ORM\Column(type: Types::STRING)]
|
||||||
|
#[Groups(['attachment:read'])]
|
||||||
|
#[ApiProperty(example: 'http://example.com/image.jpg')]
|
||||||
|
protected string $external_path = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string the name of this element
|
* @var string the name of this element
|
||||||
|
|
@ -237,7 +249,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this attachment is a picture (analyse the file's extension).
|
* Check if this attachment is a picture (analyse the file's extension).
|
||||||
* If the link is external, it is assumed that this is true.
|
* If the link is only external and doesn't contain an extension, it is assumed that this is true.
|
||||||
*
|
*
|
||||||
* @return bool * true if the file extension is a picture extension
|
* @return bool * true if the file extension is a picture extension
|
||||||
* * otherwise false
|
* * otherwise false
|
||||||
|
|
@ -245,54 +257,63 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
#[Groups(['attachment:read'])]
|
#[Groups(['attachment:read'])]
|
||||||
public function isPicture(): bool
|
public function isPicture(): bool
|
||||||
{
|
{
|
||||||
if ($this->isExternal()) {
|
if($this->hasInternal()){
|
||||||
|
|
||||||
|
$extension = pathinfo($this->getInternalPath(), PATHINFO_EXTENSION);
|
||||||
|
|
||||||
|
return in_array(strtolower($extension), static::PICTURE_EXTS, true);
|
||||||
|
|
||||||
|
}
|
||||||
|
if ($this->hasExternal()) {
|
||||||
//Check if we can extract a file extension from the URL
|
//Check if we can extract a file extension from the URL
|
||||||
$extension = pathinfo(parse_url($this->path, PHP_URL_PATH) ?? '', PATHINFO_EXTENSION);
|
$extension = pathinfo(parse_url($this->getExternalPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION);
|
||||||
|
|
||||||
//If no extension is found or it is known picture extension, we assume that this is a picture extension
|
//If no extension is found or it is known picture extension, we assume that this is a picture extension
|
||||||
return $extension === '' || in_array(strtolower($extension), static::PICTURE_EXTS, true);
|
return $extension === '' || in_array(strtolower($extension), static::PICTURE_EXTS, true);
|
||||||
}
|
}
|
||||||
|
//File doesn't have an internal, nor an external copy. This shouldn't happen, but it certainly isn't a picture...
|
||||||
$extension = pathinfo($this->getPath(), PATHINFO_EXTENSION);
|
return false;
|
||||||
|
|
||||||
return in_array(strtolower($extension), static::PICTURE_EXTS, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if this attachment is a 3D model and therefore can be directly shown to user.
|
* Check if this attachment is a 3D model and therefore can be directly shown to user.
|
||||||
* If the attachment is external, false is returned (3D Models must be internal).
|
* If no internal copy exists, false is returned (3D Models must be internal).
|
||||||
*/
|
*/
|
||||||
#[Groups(['attachment:read'])]
|
#[Groups(['attachment:read'])]
|
||||||
#[SerializedName('3d_model')]
|
#[SerializedName('3d_model')]
|
||||||
public function is3DModel(): bool
|
public function is3DModel(): bool
|
||||||
{
|
{
|
||||||
//We just assume that 3D Models are internally saved, otherwise we get problems loading them.
|
//We just assume that 3D Models are internally saved, otherwise we get problems loading them.
|
||||||
if ($this->isExternal()) {
|
if (!$this->hasInternal()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$extension = pathinfo($this->getPath(), PATHINFO_EXTENSION);
|
$extension = pathinfo($this->getInternalPath(), PATHINFO_EXTENSION);
|
||||||
|
|
||||||
return in_array(strtolower($extension), static::MODEL_EXTS, true);
|
return in_array(strtolower($extension), static::MODEL_EXTS, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the attachment file is externally saved (the database saves an URL).
|
* Checks if this attachment has a path to an external file
|
||||||
*
|
*
|
||||||
* @return bool true, if the file is saved externally
|
* @return bool true, if there is a path to an external file
|
||||||
*/
|
*/
|
||||||
#[Groups(['attachment:read'])]
|
#[Groups(['attachment:read'])]
|
||||||
public function isExternal(): bool
|
public function hasExternal(): bool
|
||||||
{
|
{
|
||||||
//When path is empty, this attachment can not be external
|
return $this->external_path !== '';
|
||||||
if ($this->path === '') {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode
|
/**
|
||||||
$tmp = explode('/', $this->path);
|
* Checks if this attachment has a path to an internal file.
|
||||||
|
* Does not check if the file exists.
|
||||||
return !in_array($tmp[0], array_merge(static::INTERNAL_PLACEHOLDER, static::BUILTIN_PLACEHOLDER), true);
|
*
|
||||||
|
* @return bool true, if there is a path to an internal file
|
||||||
|
*/
|
||||||
|
#[Groups(['attachment:read'])]
|
||||||
|
public function hasInternal(): bool
|
||||||
|
{
|
||||||
|
return $this->internal_path !== '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -306,7 +327,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
public function isSecure(): bool
|
public function isSecure(): bool
|
||||||
{
|
{
|
||||||
//After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode
|
//After the %PLACEHOLDER% comes a slash, so we can check if we have a placeholder via explode
|
||||||
$tmp = explode('/', $this->path);
|
$tmp = explode('/', $this->internal_path);
|
||||||
|
|
||||||
return '%SECURE%' === $tmp[0];
|
return '%SECURE%' === $tmp[0];
|
||||||
}
|
}
|
||||||
|
|
@ -320,7 +341,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
#[Groups(['attachment:read'])]
|
#[Groups(['attachment:read'])]
|
||||||
public function isBuiltIn(): bool
|
public function isBuiltIn(): bool
|
||||||
{
|
{
|
||||||
return static::checkIfBuiltin($this->path);
|
return static::checkIfBuiltin($this->internal_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/********************************************************************************
|
/********************************************************************************
|
||||||
|
|
@ -332,13 +353,13 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
/**
|
/**
|
||||||
* Returns the extension of the file referenced via the attachment.
|
* Returns the extension of the file referenced via the attachment.
|
||||||
* For a path like %BASE/path/foo.bar, bar will be returned.
|
* For a path like %BASE/path/foo.bar, bar will be returned.
|
||||||
* If this attachment is external null is returned.
|
* If this attachment is only external null is returned.
|
||||||
*
|
*
|
||||||
* @return string|null the file extension in lower case
|
* @return string|null the file extension in lower case
|
||||||
*/
|
*/
|
||||||
public function getExtension(): ?string
|
public function getExtension(): ?string
|
||||||
{
|
{
|
||||||
if ($this->isExternal()) {
|
if (!$this->hasInternal()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,7 +367,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
return strtolower(pathinfo($this->original_filename, PATHINFO_EXTENSION));
|
return strtolower(pathinfo($this->original_filename, PATHINFO_EXTENSION));
|
||||||
}
|
}
|
||||||
|
|
||||||
return strtolower(pathinfo($this->getPath(), PATHINFO_EXTENSION));
|
return strtolower(pathinfo($this->getInternalPath(), PATHINFO_EXTENSION));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -361,52 +382,54 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The URL to the external file, or the path to the built-in file.
|
* The URL to the external file, or the path to the built-in file, but not paths to uploaded files.
|
||||||
* Returns null, if the file is not external (and not builtin).
|
* Returns null, if the file is not external (and not builtin).
|
||||||
|
* The output of this function is such, that no changes occur when it is fed back into setURL().
|
||||||
|
* Required for the Attachment form field.
|
||||||
*/
|
*/
|
||||||
#[Groups(['attachment:read'])]
|
|
||||||
#[SerializedName('url')]
|
|
||||||
public function getURL(): ?string
|
public function getURL(): ?string
|
||||||
{
|
{
|
||||||
if (!$this->isExternal() && !$this->isBuiltIn()) {
|
if($this->hasExternal()){
|
||||||
return null;
|
return $this->getExternalPath();
|
||||||
}
|
}
|
||||||
|
if($this->isBuiltIn()){
|
||||||
return $this->path;
|
return $this->getInternalPath();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hostname where the external file is stored.
|
* Returns the hostname where the external file is stored.
|
||||||
* Returns null, if the file is not external.
|
* Returns null, if there is no external path.
|
||||||
*/
|
*/
|
||||||
public function getHost(): ?string
|
public function getHost(): ?string
|
||||||
{
|
{
|
||||||
if (!$this->isExternal()) {
|
if (!$this->hasExternal()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parse_url((string) $this->getURL(), PHP_URL_HOST);
|
return parse_url($this->getExternalPath(), PHP_URL_HOST);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getInternalPath(): string
|
||||||
* Get the filepath, relative to %BASE%.
|
|
||||||
*
|
|
||||||
* @return string A string like %BASE/path/foo.bar
|
|
||||||
*/
|
|
||||||
public function getPath(): string
|
|
||||||
{
|
{
|
||||||
return $this->path;
|
return $this->internal_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExternalPath(): string
|
||||||
|
{
|
||||||
|
return $this->external_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the filename of the attachment.
|
* Returns the filename of the attachment.
|
||||||
* For a path like %BASE/path/foo.bar, foo.bar will be returned.
|
* For a path like %BASE/path/foo.bar, foo.bar will be returned.
|
||||||
*
|
*
|
||||||
* If the path is a URL (can be checked via isExternal()), null will be returned.
|
* If there is no internal copy of the file, null will be returned.
|
||||||
*/
|
*/
|
||||||
public function getFilename(): ?string
|
public function getFilename(): ?string
|
||||||
{
|
{
|
||||||
if ($this->isExternal()) {
|
if (!$this->hasInternal()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -415,7 +438,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
return $this->original_filename;
|
return $this->original_filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
return pathinfo($this->getPath(), PATHINFO_BASENAME);
|
return pathinfo($this->getInternalPath(), PATHINFO_BASENAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -488,15 +511,12 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the filepath (with relative placeholder) for this attachment.
|
* Sets the path to a file hosted internally. If you set this path to a file that was not downloaded from the
|
||||||
*
|
* external source in external_path, make sure to reset external_path.
|
||||||
* @param string $path the new filepath of the attachment
|
|
||||||
*
|
|
||||||
* @return Attachment
|
|
||||||
*/
|
*/
|
||||||
public function setPath(string $path): self
|
public function setInternalPath(?string $internal_path): self
|
||||||
{
|
{
|
||||||
$this->path = $path;
|
$this->internal_path = $internal_path ?? '';
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
@ -512,34 +532,62 @@ abstract class Attachment extends AbstractNamedDBElement
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the url associated with this attachment.
|
* Sets up the paths using a user provided string which might contain an external path or a builtin path. Allows
|
||||||
* If the url is empty nothing is changed, to not override the file path.
|
* resetting the external path if an internal path exists. Resets any other paths if a (nonempty) new path is set.
|
||||||
*
|
|
||||||
* @return Attachment
|
|
||||||
*/
|
*/
|
||||||
#[Groups(['attachment:write'])]
|
#[Groups(['attachment:write'])]
|
||||||
#[SerializedName('url')]
|
#[SerializedName('url')]
|
||||||
|
#[ApiProperty(description: 'Set the path of the attachment here.
|
||||||
|
Provide either an external URL, a path to a builtin file (like %FOOTPRINTS%/Active/ICs/IC_DFS.png) or an empty
|
||||||
|
string if the attachment has an internal file associated and you\'d like to reset the external source.
|
||||||
|
If you set a new (nonempty) file path any associated internal file will be removed!')]
|
||||||
public function setURL(?string $url): self
|
public function setURL(?string $url): self
|
||||||
{
|
{
|
||||||
//Do nothing if the URL is empty
|
$url = $url ?? '';
|
||||||
if ($url === null || $url === '') {
|
|
||||||
|
//Don't allow the user to set an empty external path if the internal path is empty already
|
||||||
|
if ($url === '' && !$this->hasInternal()) {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = trim($url);
|
//The URL field can also contain the special builtin internal paths, so we need to distinguish here
|
||||||
//Escape spaces in URL
|
if ($this::checkIfBuiltin($url)) {
|
||||||
$url = str_replace(' ', '%20', $url);
|
$this->setInternalPath($url);
|
||||||
|
//make sure the external path isn't still pointing to something unrelated
|
||||||
//Only set if the URL is not empty
|
$this->setExternalPath('');
|
||||||
if ($url !== '') {
|
} else {
|
||||||
if (str_contains($url, '%BASE%') || str_contains($url, '%MEDIA%')) {
|
$this->setExternalPath($url);
|
||||||
throw new InvalidArgumentException('You can not reference internal files via the url field! But nice try!');
|
}
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->path = $url;
|
|
||||||
|
/**
|
||||||
|
* Sets the path to a file hosted on an external server. Setting the external path to a (nonempty) value different
|
||||||
|
* from the the old one _clears_ the internal path, so that the external path reflects where any associated internal
|
||||||
|
* file came from.
|
||||||
|
*/
|
||||||
|
public function setExternalPath(?string $external_path): self
|
||||||
|
{
|
||||||
|
//If we only clear the external path, don't reset the internal path, since that could be confusing
|
||||||
|
if($external_path === null || $external_path === '') {
|
||||||
|
$this->external_path = '';
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$external_path = trim($external_path);
|
||||||
|
//Escape spaces in URL
|
||||||
|
$external_path = str_replace(' ', '%20', $external_path);
|
||||||
|
|
||||||
|
if($this->external_path === $external_path) {
|
||||||
|
//Nothing changed, nothing to do
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->external_path = $external_path;
|
||||||
|
$this->internal_path = '';
|
||||||
//Reset internal filename
|
//Reset internal filename
|
||||||
$this->original_filename = null;
|
$this->original_filename = null;
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ class AttachmentDeleteListener
|
||||||
#[PreUpdate]
|
#[PreUpdate]
|
||||||
public function preUpdateHandler(Attachment $attachment, PreUpdateEventArgs $event): void
|
public function preUpdateHandler(Attachment $attachment, PreUpdateEventArgs $event): void
|
||||||
{
|
{
|
||||||
if ($event->hasChangedField('path')) {
|
if ($event->hasChangedField('internal_path')) {
|
||||||
$old_path = $event->getOldValue('path');
|
$old_path = $event->getOldValue('internal_path');
|
||||||
|
|
||||||
//Dont delete file if the attachment uses a builtin ressource:
|
//Dont delete file if the attachment uses a builtin ressource:
|
||||||
if (Attachment::checkIfBuiltin($old_path)) {
|
if (Attachment::checkIfBuiltin($old_path)) {
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class AttachmentRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('attachment');
|
$qb = $this->createQueryBuilder('attachment');
|
||||||
$qb->select('COUNT(attachment)')
|
$qb->select('COUNT(attachment)')
|
||||||
->where('attachment.path LIKE :like ESCAPE \'#\'');
|
->where('attachment.internal_path LIKE :like ESCAPE \'#\'');
|
||||||
$qb->setParameter('like', '#%SECURE#%%');
|
$qb->setParameter('like', '#%SECURE#%%');
|
||||||
$query = $qb->getQuery();
|
$query = $qb->getQuery();
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ class AttachmentRepository extends DBElementRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the count of all external attachments (attachments only containing a URL).
|
* Gets the count of all external attachments (attachments containing an external path).
|
||||||
*
|
*
|
||||||
* @throws NoResultException
|
* @throws NoResultException
|
||||||
* @throws NonUniqueResultException
|
* @throws NonUniqueResultException
|
||||||
|
|
@ -75,17 +75,14 @@ class AttachmentRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('attachment');
|
$qb = $this->createQueryBuilder('attachment');
|
||||||
$qb->select('COUNT(attachment)')
|
$qb->select('COUNT(attachment)')
|
||||||
->where('ILIKE(attachment.path, :http) = TRUE')
|
->where('attachment.external_path <> \'\'');
|
||||||
->orWhere('ILIKE(attachment.path, :https) = TRUE');
|
|
||||||
$qb->setParameter('http', 'http://%');
|
|
||||||
$qb->setParameter('https', 'https://%');
|
|
||||||
$query = $qb->getQuery();
|
$query = $qb->getQuery();
|
||||||
|
|
||||||
return (int) $query->getSingleScalarResult();
|
return (int) $query->getSingleScalarResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the count of all attachments where a user uploaded a file.
|
* Gets the count of all attachments where a user uploaded a file or a file was downloaded from an external source.
|
||||||
*
|
*
|
||||||
* @throws NoResultException
|
* @throws NoResultException
|
||||||
* @throws NonUniqueResultException
|
* @throws NonUniqueResultException
|
||||||
|
|
@ -94,9 +91,9 @@ class AttachmentRepository extends DBElementRepository
|
||||||
{
|
{
|
||||||
$qb = $this->createQueryBuilder('attachment');
|
$qb = $this->createQueryBuilder('attachment');
|
||||||
$qb->select('COUNT(attachment)')
|
$qb->select('COUNT(attachment)')
|
||||||
->where('attachment.path LIKE :base ESCAPE \'#\'')
|
->where('attachment.internal_path LIKE :base ESCAPE \'#\'')
|
||||||
->orWhere('attachment.path LIKE :media ESCAPE \'#\'')
|
->orWhere('attachment.internal_path LIKE :media ESCAPE \'#\'')
|
||||||
->orWhere('attachment.path LIKE :secure ESCAPE \'#\'');
|
->orWhere('attachment.internal_path LIKE :secure ESCAPE \'#\'');
|
||||||
$qb->setParameter('secure', '#%SECURE#%%');
|
$qb->setParameter('secure', '#%SECURE#%%');
|
||||||
$qb->setParameter('base', '#%BASE#%%');
|
$qb->setParameter('base', '#%BASE#%%');
|
||||||
$qb->setParameter('media', '#%MEDIA#%%');
|
$qb->setParameter('media', '#%MEDIA#%%');
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ class AttachmentNormalizer implements NormalizerInterface, NormalizerAwareInterf
|
||||||
$context[self::ALREADY_CALLED] = true;
|
$context[self::ALREADY_CALLED] = true;
|
||||||
|
|
||||||
$data = $this->normalizer->normalize($object, $format, $context);
|
$data = $this->normalizer->normalize($object, $format, $context);
|
||||||
|
$data['internal_path'] = $this->attachmentURLGenerator->getInternalViewURL($object);
|
||||||
|
|
||||||
$data['media_url'] = $this->attachmentURLGenerator->getViewURL($object);
|
|
||||||
//Add thumbnail url if the attachment is a picture
|
//Add thumbnail url if the attachment is a picture
|
||||||
$data['thumbnail_url'] = $object->isPicture() ? $this->attachmentURLGenerator->getThumbnailURL($object) : null;
|
$data['thumbnail_url'] = $object->isPicture() ? $this->attachmentURLGenerator->getThumbnailURL($object) : null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,35 +44,31 @@ class AttachmentManager
|
||||||
*
|
*
|
||||||
* @param Attachment $attachment The attachment for which the file should be generated
|
* @param Attachment $attachment The attachment for which the file should be generated
|
||||||
*
|
*
|
||||||
* @return SplFileInfo|null The fileinfo for the attachment file. Null, if the attachment is external or has
|
* @return SplFileInfo|null The fileinfo for the attachment file. Null, if the attachment is only external or has
|
||||||
* invalid file.
|
* invalid file.
|
||||||
*/
|
*/
|
||||||
public function attachmentToFile(Attachment $attachment): ?SplFileInfo
|
public function attachmentToFile(Attachment $attachment): ?SplFileInfo
|
||||||
{
|
{
|
||||||
if ($attachment->isExternal() || !$this->isFileExisting($attachment)) {
|
if (!$this->isInternalFileExisting($attachment)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SplFileInfo($this->toAbsoluteFilePath($attachment));
|
return new SplFileInfo($this->toAbsoluteInternalFilePath($attachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the absolute filepath of the attachment. Null is returned, if the attachment is externally saved,
|
* Returns the absolute filepath to the internal copy of the attachment. Null is returned, if the attachment is
|
||||||
* or is not existing.
|
* only externally saved, or is not existing.
|
||||||
*
|
*
|
||||||
* @param Attachment $attachment The attachment for which the filepath should be determined
|
* @param Attachment $attachment The attachment for which the filepath should be determined
|
||||||
*/
|
*/
|
||||||
public function toAbsoluteFilePath(Attachment $attachment): ?string
|
public function toAbsoluteInternalFilePath(Attachment $attachment): ?string
|
||||||
{
|
{
|
||||||
if ($attachment->getPath() === '') {
|
if (!$attachment->hasInternal()){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($attachment->isExternal()) {
|
$path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath());
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = $this->pathResolver->placeholderToRealPath($attachment->getPath());
|
|
||||||
|
|
||||||
//realpath does not work with null as argument
|
//realpath does not work with null as argument
|
||||||
if (null === $path) {
|
if (null === $path) {
|
||||||
|
|
@ -89,8 +85,8 @@ class AttachmentManager
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the file in this attachement is existing. This works for files on the HDD, and for URLs
|
* Checks if the file in this attachment is existing. This works for files on the HDD, and for URLs
|
||||||
* (it's not checked if the ressource behind the URL is really existing, so for every external attachment true is returned).
|
* (it's not checked if the resource behind the URL is really existing, so for every external attachment true is returned).
|
||||||
*
|
*
|
||||||
* @param Attachment $attachment The attachment for which the existence should be checked
|
* @param Attachment $attachment The attachment for which the existence should be checked
|
||||||
*
|
*
|
||||||
|
|
@ -98,15 +94,23 @@ class AttachmentManager
|
||||||
*/
|
*/
|
||||||
public function isFileExisting(Attachment $attachment): bool
|
public function isFileExisting(Attachment $attachment): bool
|
||||||
{
|
{
|
||||||
if ($attachment->getPath() === '') {
|
if($attachment->hasExternal()){
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($attachment->isExternal()) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
return $this->isInternalFileExisting($attachment);
|
||||||
|
}
|
||||||
|
|
||||||
$absolute_path = $this->toAbsoluteFilePath($attachment);
|
/**
|
||||||
|
* Checks if the internal file in this attachment is existing. Returns false if the attachment doesn't have an
|
||||||
|
* internal file.
|
||||||
|
*
|
||||||
|
* @param Attachment $attachment The attachment for which the existence should be checked
|
||||||
|
*
|
||||||
|
* @return bool true if the file is existing
|
||||||
|
*/
|
||||||
|
public function isInternalFileExisting(Attachment $attachment): bool
|
||||||
|
{
|
||||||
|
$absolute_path = $this->toAbsoluteInternalFilePath($attachment);
|
||||||
|
|
||||||
if (null === $absolute_path) {
|
if (null === $absolute_path) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -117,21 +121,17 @@ class AttachmentManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the filesize of the attachments in bytes.
|
* Returns the filesize of the attachments in bytes.
|
||||||
* For external attachments or not existing attachments, null is returned.
|
* For purely external attachments or inexistent attachments, null is returned.
|
||||||
*
|
*
|
||||||
* @param Attachment $attachment the filesize for which the filesize should be calculated
|
* @param Attachment $attachment the filesize for which the filesize should be calculated
|
||||||
*/
|
*/
|
||||||
public function getFileSize(Attachment $attachment): ?int
|
public function getFileSize(Attachment $attachment): ?int
|
||||||
{
|
{
|
||||||
if ($attachment->isExternal()) {
|
if (!$this->isInternalFileExisting($attachment)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->isFileExisting($attachment)) {
|
$tmp = filesize($this->toAbsoluteInternalFilePath($attachment));
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$tmp = filesize($this->toAbsoluteFilePath($attachment));
|
|
||||||
|
|
||||||
return false !== $tmp ? $tmp : null;
|
return false !== $tmp ? $tmp : null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class AttachmentReverseSearch
|
||||||
$repo = $this->em->getRepository(Attachment::class);
|
$repo = $this->em->getRepository(Attachment::class);
|
||||||
|
|
||||||
return $repo->findBy([
|
return $repo->findBy([
|
||||||
'path' => [$relative_path_new, $relative_path_old],
|
'internal_path' => [$relative_path_new, $relative_path_old],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ class AttachmentSubmitHandler
|
||||||
if ($file instanceof UploadedFile) {
|
if ($file instanceof UploadedFile) {
|
||||||
|
|
||||||
$this->upload($attachment, $file, $secure_attachment);
|
$this->upload($attachment, $file, $secure_attachment);
|
||||||
} elseif ($upload->downloadUrl && $attachment->isExternal()) {
|
} elseif ($upload->downloadUrl && $attachment->hasExternal()) {
|
||||||
$this->downloadURL($attachment, $secure_attachment);
|
$this->downloadURL($attachment, $secure_attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,12 +244,12 @@ class AttachmentSubmitHandler
|
||||||
protected function renameBlacklistedExtensions(Attachment $attachment): Attachment
|
protected function renameBlacklistedExtensions(Attachment $attachment): Attachment
|
||||||
{
|
{
|
||||||
//We can not do anything on builtins or external ressources
|
//We can not do anything on builtins or external ressources
|
||||||
if ($attachment->isBuiltIn() || $attachment->isExternal()) {
|
if ($attachment->isBuiltIn() || !$attachment->hasInternal()) {
|
||||||
return $attachment;
|
return $attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Determine the old filepath
|
//Determine the old filepath
|
||||||
$old_path = $this->pathResolver->placeholderToRealPath($attachment->getPath());
|
$old_path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath());
|
||||||
if ($old_path === null || $old_path === '' || !file_exists($old_path)) {
|
if ($old_path === null || $old_path === '' || !file_exists($old_path)) {
|
||||||
return $attachment;
|
return $attachment;
|
||||||
}
|
}
|
||||||
|
|
@ -267,7 +267,7 @@ class AttachmentSubmitHandler
|
||||||
$fs->rename($old_path, $new_path);
|
$fs->rename($old_path, $new_path);
|
||||||
|
|
||||||
//Update the attachment
|
//Update the attachment
|
||||||
$attachment->setPath($this->pathResolver->realPathToPlaceholder($new_path));
|
$attachment->setInternalPath($this->pathResolver->realPathToPlaceholder($new_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -275,17 +275,17 @@ class AttachmentSubmitHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the given attachment to secure location (or back to public folder) if needed.
|
* Move the internal copy of the given attachment to a secure location (or back to public folder) if needed.
|
||||||
*
|
*
|
||||||
* @param Attachment $attachment the attachment for which the file should be moved
|
* @param Attachment $attachment the attachment for which the file should be moved
|
||||||
* @param bool $secure_location this value determines, if the attachment is moved to the secure or public folder
|
* @param bool $secure_location this value determines, if the attachment is moved to the secure or public folder
|
||||||
*
|
*
|
||||||
* @return Attachment The attachment with the updated filepath
|
* @return Attachment The attachment with the updated internal filepath
|
||||||
*/
|
*/
|
||||||
protected function moveFile(Attachment $attachment, bool $secure_location): Attachment
|
protected function moveFile(Attachment $attachment, bool $secure_location): Attachment
|
||||||
{
|
{
|
||||||
//We can not do anything on builtins or external ressources
|
//We can not do anything on builtins or external ressources
|
||||||
if ($attachment->isBuiltIn() || $attachment->isExternal()) {
|
if ($attachment->isBuiltIn() || !$attachment->hasInternal()) {
|
||||||
return $attachment;
|
return $attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,7 +295,7 @@ class AttachmentSubmitHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
//Determine the old filepath
|
//Determine the old filepath
|
||||||
$old_path = $this->pathResolver->placeholderToRealPath($attachment->getPath());
|
$old_path = $this->pathResolver->placeholderToRealPath($attachment->getInternalPath());
|
||||||
if (!file_exists($old_path)) {
|
if (!file_exists($old_path)) {
|
||||||
return $attachment;
|
return $attachment;
|
||||||
}
|
}
|
||||||
|
|
@ -319,7 +319,7 @@ class AttachmentSubmitHandler
|
||||||
|
|
||||||
//Save info to attachment entity
|
//Save info to attachment entity
|
||||||
$new_path = $this->pathResolver->realPathToPlaceholder($new_path);
|
$new_path = $this->pathResolver->realPathToPlaceholder($new_path);
|
||||||
$attachment->setPath($new_path);
|
$attachment->setInternalPath($new_path);
|
||||||
|
|
||||||
return $attachment;
|
return $attachment;
|
||||||
}
|
}
|
||||||
|
|
@ -329,7 +329,7 @@ class AttachmentSubmitHandler
|
||||||
*
|
*
|
||||||
* @param bool $secureAttachment True if the file should be moved to the secure attachment storage
|
* @param bool $secureAttachment True if the file should be moved to the secure attachment storage
|
||||||
*
|
*
|
||||||
* @return Attachment The attachment with the new filepath
|
* @return Attachment The attachment with the downloaded copy
|
||||||
*/
|
*/
|
||||||
protected function downloadURL(Attachment $attachment, bool $secureAttachment): Attachment
|
protected function downloadURL(Attachment $attachment, bool $secureAttachment): Attachment
|
||||||
{
|
{
|
||||||
|
|
@ -338,7 +338,7 @@ class AttachmentSubmitHandler
|
||||||
throw new RuntimeException('Download of attachments is not allowed!');
|
throw new RuntimeException('Download of attachments is not allowed!');
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = $attachment->getURL();
|
$url = $attachment->getExternalPath();
|
||||||
|
|
||||||
$fs = new Filesystem();
|
$fs = new Filesystem();
|
||||||
$attachment_folder = $this->generateAttachmentPath($attachment, $secureAttachment);
|
$attachment_folder = $this->generateAttachmentPath($attachment, $secureAttachment);
|
||||||
|
|
@ -399,7 +399,7 @@ class AttachmentSubmitHandler
|
||||||
//Make our file path relative to %BASE%
|
//Make our file path relative to %BASE%
|
||||||
$new_path = $this->pathResolver->realPathToPlaceholder($new_path);
|
$new_path = $this->pathResolver->realPathToPlaceholder($new_path);
|
||||||
//Save the path to the attachment
|
//Save the path to the attachment
|
||||||
$attachment->setPath($new_path);
|
$attachment->setInternalPath($new_path);
|
||||||
} catch (TransportExceptionInterface) {
|
} catch (TransportExceptionInterface) {
|
||||||
throw new AttachmentDownloadException('Transport error!');
|
throw new AttachmentDownloadException('Transport error!');
|
||||||
}
|
}
|
||||||
|
|
@ -427,7 +427,9 @@ class AttachmentSubmitHandler
|
||||||
//Make our file path relative to %BASE%
|
//Make our file path relative to %BASE%
|
||||||
$file_path = $this->pathResolver->realPathToPlaceholder($file_path);
|
$file_path = $this->pathResolver->realPathToPlaceholder($file_path);
|
||||||
//Save the path to the attachment
|
//Save the path to the attachment
|
||||||
$attachment->setPath($file_path);
|
$attachment->setInternalPath($file_path);
|
||||||
|
//reset any external paths the attachment might have had
|
||||||
|
$attachment->setExternalPath('');
|
||||||
//And save original filename
|
//And save original filename
|
||||||
$attachment->setFilename($file->getClientOriginalName());
|
$attachment->setFilename($file->getClientOriginalName());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,9 +92,9 @@ class AttachmentURLGenerator
|
||||||
* Returns a URL under which the attachment file can be viewed.
|
* Returns a URL under which the attachment file can be viewed.
|
||||||
* @return string|null The URL or null if the attachment file is not existing
|
* @return string|null The URL or null if the attachment file is not existing
|
||||||
*/
|
*/
|
||||||
public function getViewURL(Attachment $attachment): ?string
|
public function getInternalViewURL(Attachment $attachment): ?string
|
||||||
{
|
{
|
||||||
$absolute_path = $this->attachmentHelper->toAbsoluteFilePath($attachment);
|
$absolute_path = $this->attachmentHelper->toAbsoluteInternalFilePath($attachment);
|
||||||
if (null === $absolute_path) {
|
if (null === $absolute_path) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -111,6 +111,7 @@ class AttachmentURLGenerator
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a URL to a thumbnail of the attachment file.
|
* Returns a URL to a thumbnail of the attachment file.
|
||||||
|
* For external files the original URL is returned.
|
||||||
* @return string|null The URL or null if the attachment file is not existing
|
* @return string|null The URL or null if the attachment file is not existing
|
||||||
*/
|
*/
|
||||||
public function getThumbnailURL(Attachment $attachment, string $filter_name = 'thumbnail_sm'): ?string
|
public function getThumbnailURL(Attachment $attachment, string $filter_name = 'thumbnail_sm'): ?string
|
||||||
|
|
@ -119,11 +120,14 @@ class AttachmentURLGenerator
|
||||||
throw new InvalidArgumentException('Thumbnail creation only works for picture attachments!');
|
throw new InvalidArgumentException('Thumbnail creation only works for picture attachments!');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($attachment->isExternal() && ($attachment->getURL() !== null && $attachment->getURL() !== '')) {
|
if (!$attachment->hasInternal()){
|
||||||
return $attachment->getURL();
|
if($attachment->hasExternal()) {
|
||||||
|
return $attachment->getExternalPath();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$absolute_path = $this->attachmentHelper->toAbsoluteFilePath($attachment);
|
$absolute_path = $this->attachmentHelper->toAbsoluteInternalFilePath($attachment);
|
||||||
if (null === $absolute_path) {
|
if (null === $absolute_path) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +141,7 @@ class AttachmentURLGenerator
|
||||||
//GD can not work with SVG, so serve it directly...
|
//GD can not work with SVG, so serve it directly...
|
||||||
//We can not use getExtension here, because it uses the original filename and not the real extension
|
//We can not use getExtension here, because it uses the original filename and not the real extension
|
||||||
//Instead we use the logic, which is also used to determine if the attachment is a picture
|
//Instead we use the logic, which is also used to determine if the attachment is a picture
|
||||||
$extension = pathinfo(parse_url($attachment->getPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION);
|
$extension = pathinfo(parse_url($attachment->getInternalPath(), PHP_URL_PATH) ?? '', PATHINFO_EXTENSION);
|
||||||
if ('svg' === $extension) {
|
if ('svg' === $extension) {
|
||||||
return $this->assets->getUrl($asset_path);
|
return $this->assets->getUrl($asset_path);
|
||||||
}
|
}
|
||||||
|
|
@ -157,7 +161,7 @@ class AttachmentURLGenerator
|
||||||
/**
|
/**
|
||||||
* Returns a download link to the file associated with the attachment.
|
* Returns a download link to the file associated with the attachment.
|
||||||
*/
|
*/
|
||||||
public function getDownloadURL(Attachment $attachment): string
|
public function getInternalDownloadURL(Attachment $attachment): string
|
||||||
{
|
{
|
||||||
//Redirect always to download controller, which sets the correct headers for downloading:
|
//Redirect always to download controller, which sets the correct headers for downloading:
|
||||||
return $this->urlGenerator->generate('attachment_download', ['id' => $attachment->getID()]);
|
return $this->urlGenerator->generate('attachment_download', ['id' => $attachment->getID()]);
|
||||||
|
|
|
||||||
|
|
@ -247,7 +247,8 @@ trait EntityMergerHelperTrait
|
||||||
{
|
{
|
||||||
return $this->mergeCollections($target, $other, 'attachments', fn(Attachment $t, Attachment $o): bool => $t->getName() === $o->getName()
|
return $this->mergeCollections($target, $other, 'attachments', fn(Attachment $t, Attachment $o): bool => $t->getName() === $o->getName()
|
||||||
&& $t->getAttachmentType() === $o->getAttachmentType()
|
&& $t->getAttachmentType() === $o->getAttachmentType()
|
||||||
&& $t->getPath() === $o->getPath());
|
&& $t->getExternalPath() === $o->getExternalPath()
|
||||||
|
&& $t->getInternalPath() === $o->getInternalPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -156,27 +156,34 @@ class EntityURLGenerator
|
||||||
|
|
||||||
public function viewURL(Attachment $entity): string
|
public function viewURL(Attachment $entity): string
|
||||||
{
|
{
|
||||||
if ($entity->isExternal()) { //For external attachments, return the link to external path
|
if ($entity->hasInternal()) {
|
||||||
return $entity->getURL() ?? throw new \RuntimeException('External attachment has no URL!');
|
return $this->attachmentURLGenerator->getInternalViewURL($entity);
|
||||||
}
|
}
|
||||||
//return $this->urlGenerator->generate('attachment_view', ['id' => $entity->getID()]);
|
|
||||||
return $this->attachmentURLGenerator->getViewURL($entity) ?? '';
|
if($entity->hasExternal()) {
|
||||||
|
return $entity->getExternalPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException('Attachment has no internal nor external path!');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadURL($entity): string
|
public function downloadURL($entity): string
|
||||||
{
|
{
|
||||||
if ($entity instanceof Attachment) {
|
if (!($entity instanceof Attachment)) {
|
||||||
if ($entity->isExternal()) { //For external attachments, return the link to external path
|
|
||||||
return $entity->getURL() ?? throw new \RuntimeException('External attachment has no URL!');
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->attachmentURLGenerator->getDownloadURL($entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Otherwise throw an error
|
|
||||||
throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', $entity::class));
|
throw new EntityNotSupportedException(sprintf('The given entity is not supported yet! Passed class type: %s', $entity::class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($entity->hasInternal()) {
|
||||||
|
return $this->attachmentURLGenerator->getInternalDownloadURL($entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($entity->hasExternal()) {
|
||||||
|
return $entity->getExternalPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \RuntimeException('Attachment has not internal or external path!');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates an URL to a page, where info about this entity can be viewed.
|
* Generates an URL to a page, where info about this entity can be viewed.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ trait PKImportHelperTrait
|
||||||
//Next comes the filename plus extension
|
//Next comes the filename plus extension
|
||||||
$path .= '/'.$attachment_row['filename'].'.'.$attachment_row['extension'];
|
$path .= '/'.$attachment_row['filename'].'.'.$attachment_row['extension'];
|
||||||
|
|
||||||
$attachment->setPath($path);
|
$attachment->setInternalPath($path);
|
||||||
|
|
||||||
return $attachment;
|
return $attachment;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -122,8 +122,8 @@ final class SandboxedTwigFactory
|
||||||
'getFullPath', 'getPathArray', 'getSubelements', 'getChildren', 'isNotSelectable', ],
|
'getFullPath', 'getPathArray', 'getSubelements', 'getChildren', 'isNotSelectable', ],
|
||||||
AbstractCompany::class => ['getAddress', 'getPhoneNumber', 'getFaxNumber', 'getEmailAddress', 'getWebsite', 'getAutoProductUrl'],
|
AbstractCompany::class => ['getAddress', 'getPhoneNumber', 'getFaxNumber', 'getEmailAddress', 'getWebsite', 'getAutoProductUrl'],
|
||||||
AttachmentContainingDBElement::class => ['getAttachments', 'getMasterPictureAttachment'],
|
AttachmentContainingDBElement::class => ['getAttachments', 'getMasterPictureAttachment'],
|
||||||
Attachment::class => ['isPicture', 'is3DModel', 'isExternal', 'isSecure', 'isBuiltIn', 'getExtension',
|
Attachment::class => ['isPicture', 'is3DModel', 'hasExternal', 'hasInternal', 'isSecure', 'isBuiltIn', 'getExtension',
|
||||||
'getElement', 'getURL', 'getHost', 'getFilename', 'getAttachmentType', 'getShowInTable', ],
|
'getElement', 'getExternalPath', 'getHost', 'getFilename', 'getAttachmentType', 'getShowInTable'],
|
||||||
AbstractParameter::class => ['getFormattedValue', 'getGroup', 'getSymbol', 'getValueMin', 'getValueMax',
|
AbstractParameter::class => ['getFormattedValue', 'getGroup', 'getSymbol', 'getValueMin', 'getValueMax',
|
||||||
'getValueTypical', 'getUnit', 'getValueText', ],
|
'getValueTypical', 'getUnit', 'getValueText', ],
|
||||||
MeasurementUnit::class => ['getUnit', 'isInteger', 'useSIPrefix'],
|
MeasurementUnit::class => ['getUnit', 'isInteger', 'useSIPrefix'],
|
||||||
|
|
|
||||||
|
|
@ -154,8 +154,14 @@
|
||||||
{% set attach = form.vars.value %}
|
{% set attach = form.vars.value %}
|
||||||
|
|
||||||
{% if attach is not null %}
|
{% if attach is not null %}
|
||||||
{% if attachment_manager.fileExisting(attach) %}
|
{% if not attach.hasInternal() %}
|
||||||
{% if not attach.external %}
|
<br><br>
|
||||||
|
<h6>
|
||||||
|
<span class="badge bg-primary">
|
||||||
|
<i class="fas fa-fw fa-globe"></i> {% trans %}attachment.external{% endtrans %}
|
||||||
|
</span>
|
||||||
|
</h6>
|
||||||
|
{% elseif attachment_manager.isInternalFileExisting(attach) %}
|
||||||
<br><br>
|
<br><br>
|
||||||
<h6>
|
<h6>
|
||||||
<span class="badge bg-primary">
|
<span class="badge bg-primary">
|
||||||
|
|
@ -166,14 +172,6 @@
|
||||||
<i class="fas fa-hdd fa-fw"></i> {{ attachment_manager.humanFileSize(attach) }}
|
<i class="fas fa-hdd fa-fw"></i> {{ attachment_manager.humanFileSize(attach) }}
|
||||||
</span>
|
</span>
|
||||||
</h6>
|
</h6>
|
||||||
{% else %}
|
|
||||||
<br><br>
|
|
||||||
<h6>
|
|
||||||
<span class="badge bg-primary">
|
|
||||||
<i class="fas fa-fw fa-globe"></i> {% trans %}attachment.external{% endtrans %}
|
|
||||||
</span>
|
|
||||||
</h6>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if attach.secure %}
|
{% if attach.secure %}
|
||||||
<h6>
|
<h6>
|
||||||
|
|
|
||||||
|
|
@ -24,18 +24,16 @@
|
||||||
<td class="align-middle">{{ attachment.name }}</td>
|
<td class="align-middle">{{ attachment.name }}</td>
|
||||||
<td class="align-middle">{{ attachment.attachmentType.fullPath }}</td>
|
<td class="align-middle">{{ attachment.attachmentType.fullPath }}</td>
|
||||||
<td class="align-middle">
|
<td class="align-middle">
|
||||||
{% if attachment.external %}
|
{% if attachment.hasInternal() %}
|
||||||
<a href="{{ attachment.uRL }}" rel="noopener" target="_blank" class="link-external">{{ attachment.host }}</a>
|
|
||||||
{% else %}
|
|
||||||
{{ attachment.filename }}
|
{{ attachment.filename }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle h6">
|
<td class="align-middle h6">
|
||||||
{% if attachment.external %}
|
{% if not attachment.hasInternal() %}
|
||||||
<span class="badge bg-primary">
|
<span class="badge bg-primary">
|
||||||
<i class="fas fa-fw fa-globe"></i> {% trans %}attachment.external{% endtrans %}
|
<i class="fas fa-fw fa-globe"></i> {% trans %}attachment.external{% endtrans %}
|
||||||
</span>
|
</span>
|
||||||
{% elseif attachment_manager.fileExisting(attachment) %}
|
{% elseif attachment_manager.internalFileExisting(attachment) %}
|
||||||
<span class="badge bg-secondary">
|
<span class="badge bg-secondary">
|
||||||
<i class="fas fa-hdd fa-fw"></i> {{ attachment_manager.humanFileSize(attachment) }}
|
<i class="fas fa-hdd fa-fw"></i> {{ attachment_manager.humanFileSize(attachment) }}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -58,13 +56,18 @@
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td><div class="btn-group" role="group" aria-label="">
|
<td><div class="btn-group" role="group" aria-label="">
|
||||||
<a {% if attachment_manager.fileExisting(attachment) %}href="{{ entity_url(attachment, 'file_view') }}"{% endif %} target="_blank"
|
<a {% if attachment.hasExternal() %}href="{{ attachment.externalPath }}"{% endif %} target="_blank"
|
||||||
class="btn btn-secondary {% if not attachment_manager.fileExisting(attachment) or (attachment.secure and not is_granted("show_private", attachment)) %}disabled{% endif %}"
|
class="btn btn-secondary {% if not attachment.hasExternal() %}disabled{% endif %}"
|
||||||
|
data-turbo="false" title="View at {{ attachment.host }}" rel="noopener"> {# TODO: translate #}
|
||||||
|
<i class="fas fa-globe fa-fw"></i>
|
||||||
|
</a>
|
||||||
|
<a {% if attachment_manager.isInternalFileExisting(attachment) %}href="{{ entity_url(attachment, 'file_view') }}"{% endif %} target="_blank"
|
||||||
|
class="btn btn-secondary {% if not attachment_manager.isInternalFileExisting(attachment) or (attachment.secure and not is_granted("show_private", attachment)) %}disabled{% endif %}"
|
||||||
data-turbo="false" title="{% trans %}attachment.view{% endtrans %}" rel="noopener">
|
data-turbo="false" title="{% trans %}attachment.view{% endtrans %}" rel="noopener">
|
||||||
<i class="fas fa-eye fa-fw"></i>
|
<i class="fas fa-eye fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
<a {% if attachment_manager.fileExisting(attachment) %}href="{{ entity_url(attachment, 'file_download') }}"{% endif %} data-turbo="false"
|
<a {% if attachment_manager.isInternalFileExisting(attachment) %}href="{{ entity_url(attachment, 'file_download') }}"{% endif %} data-turbo="false"
|
||||||
class="btn btn-secondary {% if not attachment_manager.fileExisting(attachment) or (attachment.secure and not is_granted("show_private", attachment)) %}disabled{% endif %}"
|
class="btn btn-secondary {% if not attachment_manager.isInternalFileExisting(attachment) or (attachment.secure and not is_granted("show_private", attachment)) %}disabled{% endif %}"
|
||||||
title="{% trans %}attachment.download{% endtrans %}">
|
title="{% trans %}attachment.download{% endtrans %}">
|
||||||
<i class="fas fa-download fa-fw"></i>
|
<i class="fas fa-download fa-fw"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ class AttachmentsEndpointTest extends AuthenticatedApiTestCase
|
||||||
//Attachment must be set (not null)
|
//Attachment must be set (not null)
|
||||||
$array = json_decode($response->getContent(), true);
|
$array = json_decode($response->getContent(), true);
|
||||||
|
|
||||||
self::assertNotNull($array['media_url']);
|
self::assertNotNull($array['internal_path']);
|
||||||
|
|
||||||
//Attachment must be private
|
//Attachment must be private
|
||||||
self::assertJsonContains([
|
self::assertJsonContains([
|
||||||
|
|
|
||||||
|
|
@ -59,14 +59,15 @@ class AttachmentTest extends TestCase
|
||||||
|
|
||||||
$this->assertNull($attachment->getAttachmentType());
|
$this->assertNull($attachment->getAttachmentType());
|
||||||
$this->assertFalse($attachment->isPicture());
|
$this->assertFalse($attachment->isPicture());
|
||||||
$this->assertFalse($attachment->isExternal());
|
$this->assertFalse($attachment->hasExternal());
|
||||||
|
$this->assertFalse($attachment->hasInternal());
|
||||||
$this->assertFalse($attachment->isSecure());
|
$this->assertFalse($attachment->isSecure());
|
||||||
$this->assertFalse($attachment->isBuiltIn());
|
$this->assertFalse($attachment->isBuiltIn());
|
||||||
$this->assertFalse($attachment->is3DModel());
|
$this->assertFalse($attachment->is3DModel());
|
||||||
$this->assertFalse($attachment->getShowInTable());
|
$this->assertFalse($attachment->getShowInTable());
|
||||||
$this->assertEmpty($attachment->getPath());
|
$this->assertEmpty($attachment->getInternalPath());
|
||||||
|
$this->assertEmpty($attachment->getExternalPath());
|
||||||
$this->assertEmpty($attachment->getName());
|
$this->assertEmpty($attachment->getName());
|
||||||
$this->assertEmpty($attachment->getURL());
|
|
||||||
$this->assertEmpty($attachment->getExtension());
|
$this->assertEmpty($attachment->getExtension());
|
||||||
$this->assertNull($attachment->getElement());
|
$this->assertNull($attachment->getElement());
|
||||||
$this->assertEmpty($attachment->getFilename());
|
$this->assertEmpty($attachment->getFilename());
|
||||||
|
|
@ -119,76 +120,56 @@ class AttachmentTest extends TestCase
|
||||||
$attachment->setElement($element);
|
$attachment->setElement($element);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function externalDataProvider(): \Iterator
|
|
||||||
{
|
|
||||||
yield ['', false];
|
|
||||||
yield ['%MEDIA%/foo/bar.txt', false];
|
|
||||||
yield ['%BASE%/foo/bar.jpg', false];
|
|
||||||
yield ['%FOOTPRINTS%/foo/bar.jpg', false];
|
|
||||||
yield ['%FOOTPRINTS3D%/foo/bar.jpg', false];
|
|
||||||
yield ['%SECURE%/test.txt', false];
|
|
||||||
yield ['%test%/foo/bar.ghp', true];
|
|
||||||
yield ['foo%MEDIA%/foo.jpg', true];
|
|
||||||
yield ['foo%MEDIA%/%BASE%foo.jpg', true];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider externalDataProvider
|
|
||||||
*/
|
|
||||||
public function testIsExternal($path, $expected): void
|
|
||||||
{
|
|
||||||
$attachment = new PartAttachment();
|
|
||||||
$this->setProtectedProperty($attachment, 'path', $path);
|
|
||||||
$this->assertSame($expected, $attachment->isExternal());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function extensionDataProvider(): \Iterator
|
public function extensionDataProvider(): \Iterator
|
||||||
{
|
{
|
||||||
yield ['%MEDIA%/foo/bar.txt', null, 'txt'];
|
yield ['%MEDIA%/foo/bar.txt', 'http://google.de', null, 'txt'];
|
||||||
yield ['%MEDIA%/foo/bar.JPeg', null, 'jpeg'];
|
yield ['%MEDIA%/foo/bar.JPeg', 'https://foo.bar', null, 'jpeg'];
|
||||||
yield ['%MEDIA%/foo/bar.JPeg', 'test.txt', 'txt'];
|
yield ['%MEDIA%/foo/bar.JPeg', '', 'test.txt', 'txt'];
|
||||||
yield ['%MEDIA%/foo/bar', null, ''];
|
yield ['%MEDIA%/foo/bar', 'https://foo.bar/test.jpeg', null, ''];
|
||||||
yield ['%MEDIA%/foo.bar', 'bar', ''];
|
yield ['%MEDIA%/foo.bar', 'test.txt', 'bar', ''];
|
||||||
yield ['http://google.de', null, null];
|
yield ['', 'http://google.de', null, null];
|
||||||
yield ['https://foo.bar', null, null];
|
yield ['', 'https://foo.bar', null, null];
|
||||||
yield ['https://foo.bar/test.jpeg', null, null];
|
yield ['', ',https://foo.bar/test.jpeg', null, null];
|
||||||
yield ['test', null, null];
|
yield ['', 'test', null, null];
|
||||||
yield ['test.txt', null, null];
|
yield ['', 'test.txt', null, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider extensionDataProvider
|
* @dataProvider extensionDataProvider
|
||||||
*/
|
*/
|
||||||
public function testGetExtension($path, $originalFilename, $expected): void
|
public function testGetExtension($internal_path, $external_path, $originalFilename, $expected): void
|
||||||
{
|
{
|
||||||
$attachment = new PartAttachment();
|
$attachment = new PartAttachment();
|
||||||
$this->setProtectedProperty($attachment, 'path', $path);
|
$this->setProtectedProperty($attachment, 'internal_path', $internal_path);
|
||||||
|
$this->setProtectedProperty($attachment, 'external_path', $external_path);
|
||||||
$this->setProtectedProperty($attachment, 'original_filename', $originalFilename);
|
$this->setProtectedProperty($attachment, 'original_filename', $originalFilename);
|
||||||
$this->assertSame($expected, $attachment->getExtension());
|
$this->assertSame($expected, $attachment->getExtension());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function pictureDataProvider(): \Iterator
|
public function pictureDataProvider(): \Iterator
|
||||||
{
|
{
|
||||||
yield ['%MEDIA%/foo/bar.txt', false];
|
yield ['', '%MEDIA%/foo/bar.txt', false];
|
||||||
yield ['https://test.de/picture.jpeg', true];
|
yield ['', 'https://test.de/picture.jpeg', true];
|
||||||
yield ['https://test.de/picture.png?test=fdsj&width=34', true];
|
yield ['', 'https://test.de/picture.png?test=fdsj&width=34', true];
|
||||||
yield ['https://invalid.invalid/file.txt', false];
|
yield ['', 'https://invalid.invalid/file.txt', false];
|
||||||
yield ['http://infsf.inda/file.zip?test', false];
|
yield ['', 'http://infsf.inda/file.zip?test', false];
|
||||||
yield ['https://test.de', true];
|
yield ['', 'https://test.de', true];
|
||||||
yield ['https://invalid.com/invalid/pic', true];
|
yield ['', 'https://invalid.com/invalid/pic', true];
|
||||||
yield ['%MEDIA%/foo/bar.jpeg', true];
|
yield ['%MEDIA%/foo/bar.jpeg', 'https://invalid.invalid/file.txt', true];
|
||||||
yield ['%MEDIA%/foo/bar.webp', true];
|
yield ['%MEDIA%/foo/bar.webp', '', true];
|
||||||
yield ['%MEDIA%/foo', false];
|
yield ['%MEDIA%/foo', '', false];
|
||||||
yield ['%SECURE%/foo.txt/test', false];
|
yield ['%SECURE%/foo.txt/test', 'https://test.de/picture.jpeg', false];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider pictureDataProvider
|
* @dataProvider pictureDataProvider
|
||||||
*/
|
*/
|
||||||
public function testIsPicture($path, $expected): void
|
public function testIsPicture($internal_path, $external_path, $expected): void
|
||||||
{
|
{
|
||||||
$attachment = new PartAttachment();
|
$attachment = new PartAttachment();
|
||||||
$this->setProtectedProperty($attachment, 'path', $path);
|
$this->setProtectedProperty($attachment, 'internal_path', $internal_path);
|
||||||
|
$this->setProtectedProperty($attachment, 'external_path', $external_path);
|
||||||
$this->assertSame($expected, $attachment->isPicture());
|
$this->assertSame($expected, $attachment->isPicture());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,7 +189,7 @@ class AttachmentTest extends TestCase
|
||||||
public function testIsBuiltIn($path, $expected): void
|
public function testIsBuiltIn($path, $expected): void
|
||||||
{
|
{
|
||||||
$attachment = new PartAttachment();
|
$attachment = new PartAttachment();
|
||||||
$this->setProtectedProperty($attachment, 'path', $path);
|
$this->setProtectedProperty($attachment, 'internal_path', $path);
|
||||||
$this->assertSame($expected, $attachment->isBuiltIn());
|
$this->assertSame($expected, $attachment->isBuiltIn());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,52 +206,56 @@ class AttachmentTest extends TestCase
|
||||||
public function testGetHost($path, $expected): void
|
public function testGetHost($path, $expected): void
|
||||||
{
|
{
|
||||||
$attachment = new PartAttachment();
|
$attachment = new PartAttachment();
|
||||||
$this->setProtectedProperty($attachment, 'path', $path);
|
$this->setProtectedProperty($attachment, 'external_path', $path);
|
||||||
$this->assertSame($expected, $attachment->getHost());
|
$this->assertSame($expected, $attachment->getHost());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function filenameProvider(): \Iterator
|
public function filenameProvider(): \Iterator
|
||||||
{
|
{
|
||||||
yield ['%MEDIA%/foo/bar.txt', null, 'bar.txt'];
|
yield ['%MEDIA%/foo/bar.txt', 'https://www.google.de/test.txt', null, 'bar.txt'];
|
||||||
yield ['%MEDIA%/foo/bar.JPeg', 'test.txt', 'test.txt'];
|
yield ['%MEDIA%/foo/bar.JPeg', 'https://www.google.de/foo.txt', 'test.txt', 'test.txt'];
|
||||||
yield ['https://www.google.de/test.txt', null, null];
|
yield ['', 'https://www.google.de/test.txt', null, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dataProvider filenameProvider
|
* @dataProvider filenameProvider
|
||||||
*/
|
*/
|
||||||
public function testGetFilename($path, $original_filename, $expected): void
|
public function testGetFilename($internal_path, $external_path, $original_filename, $expected): void
|
||||||
{
|
{
|
||||||
$attachment = new PartAttachment();
|
$attachment = new PartAttachment();
|
||||||
$this->setProtectedProperty($attachment, 'path', $path);
|
$this->setProtectedProperty($attachment, 'internal_path', $internal_path);
|
||||||
|
$this->setProtectedProperty($attachment, 'external_path', $external_path);
|
||||||
$this->setProtectedProperty($attachment, 'original_filename', $original_filename);
|
$this->setProtectedProperty($attachment, 'original_filename', $original_filename);
|
||||||
$this->assertSame($expected, $attachment->getFilename());
|
$this->assertSame($expected, $attachment->getFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetURL(): void
|
public function testSetExternalPath(): void
|
||||||
{
|
{
|
||||||
$attachment = new PartAttachment();
|
$attachment = new PartAttachment();
|
||||||
|
|
||||||
//Set URL
|
//Set URL
|
||||||
$attachment->setURL('https://google.de');
|
$attachment->setExternalPath('https://google.de');
|
||||||
$this->assertSame('https://google.de', $attachment->getURL());
|
$this->assertSame('https://google.de', $attachment->getExternalPath());
|
||||||
|
|
||||||
//Ensure that an empty url does not overwrite the existing one
|
//Ensure that changing the external path does reset the internal one
|
||||||
$attachment->setPath('%MEDIA%/foo/bar.txt');
|
$attachment->setInternalPath('%MEDIA%/foo/bar.txt');
|
||||||
$attachment->setURL(' ');
|
$attachment->setExternalPath('https://example.de');
|
||||||
$this->assertSame('%MEDIA%/foo/bar.txt', $attachment->getPath());
|
$this->assertSame('', $attachment->getInternalPath());
|
||||||
|
|
||||||
|
//Ensure that setting the same value to the external path again doesn't reset the internal one
|
||||||
|
$attachment->setExternalPath('https://google.de');
|
||||||
|
$attachment->setInternalPath('%MEDIA%/foo/bar.txt');
|
||||||
|
$attachment->setExternalPath('https://google.de');
|
||||||
|
$this->assertSame('%MEDIA%/foo/bar.txt', $attachment->getInternalPath());
|
||||||
|
|
||||||
|
//Ensure that resetting the external path doesn't reset the internal one
|
||||||
|
$attachment->setInternalPath('%MEDIA%/foo/bar.txt');
|
||||||
|
$attachment->setExternalPath('');
|
||||||
|
$this->assertSame('%MEDIA%/foo/bar.txt', $attachment->getInternalPath());
|
||||||
|
|
||||||
//Ensure that spaces get replaced by %20
|
//Ensure that spaces get replaced by %20
|
||||||
$attachment->setURL('https://google.de/test file.txt');
|
$attachment->setExternalPath('https://example.de/test file.txt');
|
||||||
$this->assertSame('https://google.de/test%20file.txt', $attachment->getURL());
|
$this->assertSame('https://example.de/test%20file.txt', $attachment->getExternalPath());
|
||||||
}
|
|
||||||
|
|
||||||
public function testSetURLForbiddenURL(): void
|
|
||||||
{
|
|
||||||
$attachment = new PartAttachment();
|
|
||||||
|
|
||||||
$this->expectException(InvalidArgumentException::class);
|
|
||||||
$attachment->setURL('%MEDIA%/foo/bar.txt');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIsURL(): void
|
public function testIsURL(): void
|
||||||
|
|
|
||||||
|
|
@ -791,7 +791,7 @@ The user will have to set up all two-factor authentication methods again and pri
|
||||||
</notes>
|
</notes>
|
||||||
<segment state="translated">
|
<segment state="translated">
|
||||||
<source>attachment.external</source>
|
<source>attachment.external</source>
|
||||||
<target>External</target>
|
<target>External only</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="JES0hrm" name="attachment.preview.alt">
|
<unit id="JES0hrm" name="attachment.preview.alt">
|
||||||
|
|
@ -817,7 +817,7 @@ The user will have to set up all two-factor authentication methods again and pri
|
||||||
</notes>
|
</notes>
|
||||||
<segment state="translated">
|
<segment state="translated">
|
||||||
<source>attachment.view</source>
|
<source>attachment.view</source>
|
||||||
<target>View</target>
|
<target>View Local Copy</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="mEHEYM6" name="attachment.file_not_found">
|
<unit id="mEHEYM6" name="attachment.file_not_found">
|
||||||
|
|
@ -2126,7 +2126,7 @@ Sub elements will be moved upwards.</target>
|
||||||
</notes>
|
</notes>
|
||||||
<segment state="translated">
|
<segment state="translated">
|
||||||
<source>attachment.download</source>
|
<source>attachment.download</source>
|
||||||
<target>Download</target>
|
<target>Download Local Copy</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="mPK9Iyq" name="user.creating_user">
|
<unit id="mPK9Iyq" name="user.creating_user">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue