mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-15 14:09:36 +00:00
Added the option to only show attachment types for certain element classes
This commit is contained in:
parent
2c56ec746c
commit
a4c2b8f885
8 changed files with 231 additions and 5 deletions
|
|
@ -136,7 +136,7 @@ abstract class Attachment extends AbstractNamedDBElement
|
|||
* @var string The class of the element that can be passed to this attachment. Must be overridden in subclasses.
|
||||
* @phpstan-var class-string<T>
|
||||
*/
|
||||
protected const ALLOWED_ELEMENT_CLASS = AttachmentContainingDBElement::class;
|
||||
public const ALLOWED_ELEMENT_CLASS = AttachmentContainingDBElement::class;
|
||||
|
||||
/**
|
||||
* @var AttachmentUpload|null The options used for uploading a file to this attachment or modify it.
|
||||
|
|
|
|||
|
|
@ -135,11 +135,16 @@ class AttachmentType extends AbstractStructuralDBElement
|
|||
protected Collection $attachments_with_type;
|
||||
|
||||
/**
|
||||
* @var array|null A list of allowed targets where this attachment type can be assigned to.
|
||||
* @var string[]|null A list of allowed targets where this attachment type can be assigned to, as a list of portable names
|
||||
*/
|
||||
#[ORM\Column(type: Types::SIMPLE_ARRAY, nullable: true)]
|
||||
protected ?array $allowed_targets = null;
|
||||
|
||||
/**
|
||||
* @var class-string<Attachment>[]|null
|
||||
*/
|
||||
protected ?array $allowed_targets_parsed_cache = null;
|
||||
|
||||
#[Groups(['attachment_type:read'])]
|
||||
protected ?\DateTimeImmutable $addedDate = null;
|
||||
#[Groups(['attachment_type:read'])]
|
||||
|
|
@ -190,4 +195,81 @@ class AttachmentType extends AbstractStructuralDBElement
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of allowed targets as class names (e.g. PartAttachment::class), where this attachment type can be assigned to. If null, there are no restrictions.
|
||||
* @return class-string<Attachment>[]|null
|
||||
*/
|
||||
public function getAllowedTargets(): ?array
|
||||
{
|
||||
//Use cached value if available
|
||||
if ($this->allowed_targets_parsed_cache !== null) {
|
||||
return $this->allowed_targets_parsed_cache;
|
||||
}
|
||||
|
||||
if (empty($this->allowed_targets)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tmp = [];
|
||||
foreach ($this->allowed_targets as $target) {
|
||||
if (Attachment::ORM_DISCRIMINATOR_MAP[$target]) {
|
||||
$tmp[] = Attachment::ORM_DISCRIMINATOR_MAP[$target];
|
||||
}
|
||||
//Otherwise ignore the entry, as it is invalid
|
||||
}
|
||||
|
||||
//Cache the parsed value
|
||||
$this->allowed_targets_parsed_cache = $tmp;
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the allowed targets for this attachment type. Allowed targets are specified as a list of class names (e.g. PartAttachment::class). If null is passed, there are no restrictions.
|
||||
* @param class-string<Attachment>[]|null $allowed_targets
|
||||
* @return $this
|
||||
*/
|
||||
public function setAllowedTargets(?array $allowed_targets): self
|
||||
{
|
||||
if ($allowed_targets === null) {
|
||||
$this->allowed_targets = null;
|
||||
} else {
|
||||
$tmp = [];
|
||||
foreach ($allowed_targets as $target) {
|
||||
$discriminator = array_search($target, Attachment::ORM_DISCRIMINATOR_MAP, true);
|
||||
if ($discriminator !== false) {
|
||||
$tmp[] = $discriminator;
|
||||
} else {
|
||||
throw new \InvalidArgumentException("Invalid allowed target: $target. Allowed targets must be a class name of an Attachment subclass.");
|
||||
}
|
||||
}
|
||||
$this->allowed_targets = $tmp;
|
||||
}
|
||||
|
||||
//Reset the cache
|
||||
$this->allowed_targets_parsed_cache = null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this attachment type is allowed for the given attachment target.
|
||||
* @param Attachment|string $attachment
|
||||
* @return bool
|
||||
*/
|
||||
public function isAllowedForTarget(Attachment|string $attachment): bool
|
||||
{
|
||||
//If no restrictions are set, allow all targets
|
||||
if ($this->getAllowedTargets() === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//Iterate over all allowed targets and check if the attachment is an instance of any of them
|
||||
foreach ($this->getAllowedTargets() as $allowed_target) {
|
||||
if (is_a($attachment, $allowed_target, true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,17 +22,23 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form\AdminPages;
|
||||
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Attachments\PartAttachment;
|
||||
use App\Entity\Attachments\ProjectAttachment;
|
||||
use App\Services\ElementTypeNameGenerator;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Base\AbstractNamedDBElement;
|
||||
use App\Services\Attachments\FileTypeFilterTools;
|
||||
use App\Services\LogSystem\EventCommentNeededHelper;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Translation\StaticMessage;
|
||||
|
||||
class AttachmentTypeAdminForm extends BaseEntityAdminForm
|
||||
{
|
||||
public function __construct(Security $security, protected FileTypeFilterTools $filterTools, EventCommentNeededHelper $eventCommentNeededHelper)
|
||||
public function __construct(Security $security, protected FileTypeFilterTools $filterTools, EventCommentNeededHelper $eventCommentNeededHelper, private readonly ElementTypeNameGenerator $elementTypeNameGenerator)
|
||||
{
|
||||
parent::__construct($security, $eventCommentNeededHelper);
|
||||
}
|
||||
|
|
@ -41,6 +47,25 @@ class AttachmentTypeAdminForm extends BaseEntityAdminForm
|
|||
{
|
||||
$is_new = null === $entity->getID();
|
||||
|
||||
|
||||
$choiceLabel = function (string $class) {
|
||||
if (!is_a($class, Attachment::class, true)) {
|
||||
return $class;
|
||||
}
|
||||
return new StaticMessage($this->elementTypeNameGenerator->typeLabel($class::ALLOWED_ELEMENT_CLASS));
|
||||
};
|
||||
|
||||
|
||||
$builder->add('allowed_targets', ChoiceType::class, [
|
||||
'required' => false,
|
||||
'choices' => array_values(Attachment::ORM_DISCRIMINATOR_MAP),
|
||||
'choice_label' => $choiceLabel,
|
||||
'preferred_choices' => [PartAttachment::class, ProjectAttachment::class],
|
||||
'label' => 'attachment_type.edit.allowed_targets',
|
||||
'help' => 'attachment_type.edit.allowed_targets.help',
|
||||
'multiple' => true,
|
||||
]);
|
||||
|
||||
$builder->add('filetype_filter', TextType::class, [
|
||||
'required' => false,
|
||||
'label' => 'attachment_type.edit.filetype_filter',
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Form\Type\AttachmentTypeType;
|
||||
use App\Settings\SystemSettings\AttachmentsSettings;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
|
|
@ -67,10 +68,10 @@ class AttachmentFormType extends AbstractType
|
|||
'required' => false,
|
||||
'empty_data' => '',
|
||||
])
|
||||
->add('attachment_type', StructuralEntityType::class, [
|
||||
->add('attachment_type', AttachmentTypeType::class, [
|
||||
'label' => 'attachment.edit.attachment_type',
|
||||
'class' => AttachmentType::class,
|
||||
'disable_not_selectable' => true,
|
||||
'attachment_filter_class' => $options['data_class'] ?? null,
|
||||
'allow_add' => $this->security->isGranted('@attachment_types.create'),
|
||||
]);
|
||||
|
||||
|
|
|
|||
56
src/Form/Type/AttachmentTypeType.php
Normal file
56
src/Form/Type/AttachmentTypeType.php
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2026 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 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* Form type to select the AttachmentType to use in an attachment form. This is used to filter the available attachment types based on the target class.
|
||||
*/
|
||||
class AttachmentTypeType extends AbstractType
|
||||
{
|
||||
public function getParent(): ?string
|
||||
{
|
||||
return StructuralEntityType::class;
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->define('attachment_filter_class')->allowedTypes('null', 'string')->default(null);
|
||||
|
||||
$resolver->setDefault('class', AttachmentType::class);
|
||||
|
||||
$resolver->setDefault('choice_filter', function (Options $options) {
|
||||
if (is_a($options['class'], AttachmentType::class, true) && $options['attachment_filter_class'] !== null) {
|
||||
return static function (?AttachmentType $choice) use ($options) {
|
||||
return $choice?->isAllowedForTarget($options['attachment_filter_class']);
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
{% block additional_controls %}
|
||||
{{ form_row(form.filetype_filter) }}
|
||||
{{ form_row(form.allowed_targets) }}
|
||||
{{ form_row(form.alternative_names) }}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ declare(strict_types=1);
|
|||
namespace App\Tests\Entity\Attachments;
|
||||
|
||||
use App\Entity\Attachments\AttachmentType;
|
||||
use App\Entity\Attachments\PartAttachment;
|
||||
use App\Entity\Attachments\UserAttachment;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
|
|
@ -34,4 +36,51 @@ class AttachmentTypeTest extends TestCase
|
|||
$this->assertInstanceOf(Collection::class, $attachment_type->getAttachmentsForType());
|
||||
$this->assertEmpty($attachment_type->getFiletypeFilter());
|
||||
}
|
||||
|
||||
public function testSetAllowedTargets(): void
|
||||
{
|
||||
$attachmentType = new AttachmentType();
|
||||
|
||||
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$attachmentType->setAllowedTargets(['target1', 'target2']);
|
||||
}
|
||||
|
||||
public function testGetSetAllowedTargets(): void
|
||||
{
|
||||
$attachmentType = new AttachmentType();
|
||||
|
||||
$attachmentType->setAllowedTargets([PartAttachment::class, UserAttachment::class]);
|
||||
$this->assertSame([PartAttachment::class, UserAttachment::class], $attachmentType->getAllowedTargets());
|
||||
//Caching should also work
|
||||
$this->assertSame([PartAttachment::class, UserAttachment::class], $attachmentType->getAllowedTargets());
|
||||
|
||||
//Setting null should reset the allowed targets
|
||||
$attachmentType->setAllowedTargets(null);
|
||||
$this->assertNull($attachmentType->getAllowedTargets());
|
||||
}
|
||||
|
||||
public function testIsAllowedForTarget(): void
|
||||
{
|
||||
$attachmentType = new AttachmentType();
|
||||
|
||||
//By default, all targets should be allowed
|
||||
$this->assertTrue($attachmentType->isAllowedForTarget(PartAttachment::class));
|
||||
$this->assertTrue($attachmentType->isAllowedForTarget(UserAttachment::class));
|
||||
|
||||
//Set specific allowed targets
|
||||
$attachmentType->setAllowedTargets([PartAttachment::class]);
|
||||
$this->assertTrue($attachmentType->isAllowedForTarget(PartAttachment::class));
|
||||
$this->assertFalse($attachmentType->isAllowedForTarget(UserAttachment::class));
|
||||
|
||||
//Set both targets
|
||||
$attachmentType->setAllowedTargets([PartAttachment::class, UserAttachment::class]);
|
||||
$this->assertTrue($attachmentType->isAllowedForTarget(PartAttachment::class));
|
||||
$this->assertTrue($attachmentType->isAllowedForTarget(UserAttachment::class));
|
||||
|
||||
//Reset allowed targets
|
||||
$attachmentType->setAllowedTargets(null);
|
||||
$this->assertTrue($attachmentType->isAllowedForTarget(PartAttachment::class));
|
||||
$this->assertTrue($attachmentType->isAllowedForTarget(UserAttachment::class));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12425,5 +12425,17 @@ Buerklin-API Authentication server:
|
|||
<target>GTIN / EAN barcode</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="cmchX59" name="attachment_type.edit.allowed_targets">
|
||||
<segment>
|
||||
<source>attachment_type.edit.allowed_targets</source>
|
||||
<target>Use only for</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="t5R8p1l" name="attachment_type.edit.allowed_targets.help">
|
||||
<segment>
|
||||
<source>attachment_type.edit.allowed_targets.help</source>
|
||||
<target>Make this attachment type only available for certain element classes. Leave empty to show this attachment type for all element classes.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
</file>
|
||||
</xliff>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue