diff --git a/src/DataTables/Column/EntityColumn.php b/src/DataTables/Column/EntityColumn.php index 00f095f8..ccab11f6 100644 --- a/src/DataTables/Column/EntityColumn.php +++ b/src/DataTables/Column/EntityColumn.php @@ -32,6 +32,7 @@ namespace App\DataTables\Column; +use App\Entity\Base\DBElement; use App\Entity\Base\NamedDBElement; use App\Entity\Parts\Part; use App\Services\EntityURLGenerator; @@ -76,14 +77,19 @@ class EntityColumn extends AbstractColumn $resolver->setDefault('render', function (Options $options) { return function ($value, Part $context) use ($options) { + /** @var DBElement $entity */ $entity = $this->accessor->getValue($context, $options['property']); if ($entity) { - return sprintf( - '%s', - $this->urlGenerator->listPartsURL($entity), - $value - ); + if ($entity->getID() !== null) { + return sprintf( + '%s', + $this->urlGenerator->listPartsURL($entity), + $value + ); + } else { + return sprintf('%s', $value); + } } }; }); diff --git a/src/Entity/Parts/Part.php b/src/Entity/Parts/Part.php index 2c2bc58d..48a74693 100644 --- a/src/Entity/Parts/Part.php +++ b/src/Entity/Parts/Part.php @@ -74,7 +74,10 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** - * Class Part. + * Part class. + * + * DONT USE orphanRemoval on properties with ColumnSecurity!! An empty collection will be created as placeholder, + * and the partlots are deleted, even if we want dont want that! * * @ORM\Entity(repositoryClass="App\Repository\PartRepository") * @ORM\Table("`parts`") @@ -84,7 +87,8 @@ class Part extends AttachmentContainingDBElement public const INSTOCK_UNKNOWN = -2; /** - * @ORM\OneToMany(targetEntity="App\Entity\Attachments\PartAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) + * @ORM\OneToMany(targetEntity="App\Entity\Attachments\PartAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=false) + * @ColumnSecurity(type="collection", prefix="attachments") * @Assert\Valid() */ protected $attachments; @@ -93,6 +97,7 @@ class Part extends AttachmentContainingDBElement * @var Category * @ORM\ManyToOne(targetEntity="Category", inversedBy="parts") * @ORM\JoinColumn(name="id_category", referencedColumnName="id", nullable=false) + * @ColumnSecurity(prefix="category", type="App\Entity\Parts\Category") * @Selectable() */ protected $category; @@ -101,8 +106,7 @@ class Part extends AttachmentContainingDBElement * @var Footprint|null * @ORM\ManyToOne(targetEntity="Footprint", inversedBy="parts") * @ORM\JoinColumn(name="id_footprint", referencedColumnName="id") - * - * @ColumnSecurity(prefix="footprint", type="object") + * @ColumnSecurity(prefix="footprint", type="App\Entity\Parts\Footprint") * @Selectable() */ protected $footprint; @@ -111,8 +115,7 @@ class Part extends AttachmentContainingDBElement * @var Manufacturer|null * @ORM\ManyToOne(targetEntity="Manufacturer", inversedBy="parts") * @ORM\JoinColumn(name="id_manufacturer", referencedColumnName="id") - * - * @ColumnSecurity(prefix="manufacturer", type="object") + * @ColumnSecurity(prefix="manufacturer", type="App\Entity\Parts\Manufacturer") * @Selectable() */ protected $manufacturer; @@ -128,7 +131,7 @@ class Part extends AttachmentContainingDBElement /** * @var Orderdetail[] - * @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true) + * @ORM\OneToMany(targetEntity="App\Entity\PriceInformations\Orderdetail", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=false) * @Assert\Valid() * @ColumnSecurity(prefix="orderdetails", type="collection") */ @@ -174,15 +177,15 @@ class Part extends AttachmentContainingDBElement /** * @var string * @ORM\Column(type="text") - * * @ColumnSecurity(prefix="description") */ protected $description = ''; /** * @var ?PartLot[] - * @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true) + * @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=false) * @Assert\Valid() + * @ColumnSecurity(type="collection", prefix="lots") */ protected $partLots; @@ -233,14 +236,14 @@ class Part extends AttachmentContainingDBElement * @var string * @ORM\Column(type="string") * @Assert\Url() - * @ColumnSecurity(prefix="manufacturer", type="string", placeholder="") + * @ColumnSecurity(prefix="mpn", type="string", placeholder="") */ protected $manufacturer_product_url = ''; /** * @var string * @ORM\Column(type="string") - * @ColumnSecurity(prefix="manufacturer", type="string", placeholder="") + * @ColumnSecurity(prefix="mpn", type="string", placeholder="") */ protected $manufacturer_product_number = ''; @@ -248,12 +251,14 @@ class Part extends AttachmentContainingDBElement * @var string * @ORM\Column(type="string", length=255, nullable=true) * @Assert\Choice({"announced", "active", "nrfnd", "eol", "discontinued", ""}) + * @ColumnSecurity(type="string", prefix="status", placeholder="") */ protected $manufacturing_status = ""; /** * @var bool Determines if this part entry needs review (for example, because it is work in progress) * @ORM\Column(type="boolean") + * @ColumnSecurity(type="boolean") */ protected $needs_review = false; @@ -261,18 +266,21 @@ class Part extends AttachmentContainingDBElement * @var ?MeasurementUnit The unit in which the part's amount is measured. * @ORM\ManyToOne(targetEntity="MeasurementUnit", inversedBy="parts") * @ORM\JoinColumn(name="id_part_unit", referencedColumnName="id", nullable=true) + * @ColumnSecurity(type="object", prefix="unit") */ protected $partUnit; /** * @var string A comma seperated list of tags, assocciated with the part. * @ORM\Column(type="text") + * @ColumnSecurity(type="string", prefix="tags", placeholder="") */ protected $tags = ''; /** * @var float|null How much a single part unit weighs in gramms. * @ORM\Column(type="float", nullable=true) + * @ColumnSecurity(type="float", placeholder=null) * @Assert\PositiveOrZero() */ protected $mass; @@ -482,7 +490,7 @@ class Part extends AttachmentContainingDBElement * * @return Category the category of this part */ - public function getCategory(): Category + public function getCategory(): ?Category { return $this->category; } diff --git a/src/Form/Part/PartBaseType.php b/src/Form/Part/PartBaseType.php index d9866f65..ff3769af 100644 --- a/src/Form/Part/PartBaseType.php +++ b/src/Form/Part/PartBaseType.php @@ -41,7 +41,6 @@ use App\Entity\Parts\Part; use App\Entity\Parts\Storelocation; use App\Entity\PriceInformations\Orderdetail; use App\Form\AttachmentFormType; -use App\Form\AttachmentType; use App\Form\Type\SIUnitType; use App\Form\Type\StructuralEntityType; use Doctrine\DBAL\Types\FloatType; @@ -122,7 +121,7 @@ class PartBaseType extends AbstractType 'required' => false, 'label' => $this->trans->trans('part.edit.footprint'), 'disable_not_selectable' => true, - 'disabled' => !$this->security->isGranted('move', $part), + 'disabled' => !$this->security->isGranted('footprint.edit', $part), ]) ->add('tags', TextType::class, [ 'required' => false, diff --git a/src/Security/Annotations/ColumnSecurity.php b/src/Security/Annotations/ColumnSecurity.php index 6a35eb39..7138410e 100644 --- a/src/Security/Annotations/ColumnSecurity.php +++ b/src/Security/Annotations/ColumnSecurity.php @@ -29,6 +29,7 @@ namespace App\Security\Annotations; +use App\Entity\Base\NamedDBElement; use Doctrine\Common\Annotations\Annotation; use Doctrine\Common\Collections\ArrayCollection; use \InvalidArgumentException; @@ -90,10 +91,25 @@ class ColumnSecurity public function getPlaceholder() { + //Check if a class name was specified + if (class_exists($this->type)) { + $object = new $this->type(); + if ($object instanceof NamedDBElement) { + if (is_string($this->placeholder) && $this->placeholder !== "") { + $object->setName($this->placeholder); + } + $object->setName('???'); + } + return $object; + } + + if (null === $this->placeholder) { switch ($this->type) { case 'integer': return 0; + case 'float': + return 0.0; case 'string': return '???'; case 'object': diff --git a/src/Security/EntityListeners/ElementPermissionListener.php b/src/Security/EntityListeners/ElementPermissionListener.php index 63136691..011ff9b8 100644 --- a/src/Security/EntityListeners/ElementPermissionListener.php +++ b/src/Security/EntityListeners/ElementPermissionListener.php @@ -33,7 +33,11 @@ use App\Entity\Base\DBElement; use App\Security\Annotations\ColumnSecurity; use Doctrine\Common\Annotations\Reader; use Doctrine\Common\Persistence\Event\LifecycleEventArgs; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Event\PreFlushEventArgs; use Doctrine\ORM\Event\PreUpdateEventArgs; +use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping\PostLoad; use Doctrine\ORM\Mapping\PreUpdate; use ReflectionClass; @@ -50,11 +54,13 @@ class ElementPermissionListener { protected $security; protected $reader; + protected $em; - public function __construct(Security $security, Reader $reader) + public function __construct(Security $security, Reader $reader, EntityManagerInterface $em) { $this->security = $security; $this->reader = $reader; + $this->em = $em; } /** @@ -80,7 +86,39 @@ class ElementPermissionListener //Check if user is allowed to read info, otherwise apply placeholder if ((null !== $annotation) && !$this->security->isGranted($annotation->getReadOperationName(), $element)) { $property->setAccessible(true); - $property->setValue($element, $annotation->getPlaceholder()); + $value = $annotation->getPlaceholder(); + if($value instanceof DBElement) { + $this->em->detach($value); + } + $property->setValue($element, $value); + } + } + } + + /** + * @ORM\PreFlush() + * This function is called before flushing. We use it, to remove all placeholder DBElements (with name=???), + * so we dont get a no cascade persistance error. + */ + public function preFlushHandler(DBElement $element, PreFlushEventArgs $eventArgs) + { + //$eventArgs->getEntityManager()->getUnitOfWork()-> + + $reflectionClass = new ReflectionClass($element); + $properties = $reflectionClass->getProperties(); + + foreach ($properties as $property) { + $annotation = $this->reader->getPropertyAnnotation( + $property, + ColumnSecurity::class + ); + if (null !== $annotation) { + //Check if the current property is an DBElement + $property->setAccessible(true); + $value = $property->getValue($element); + if ($value instanceof DBElement && !$this->security->isGranted($annotation->getEditOperationName(), $element)) { + $property->setValue($element, null); + } } } } @@ -97,7 +135,7 @@ class ElementPermissionListener foreach ($properties as $property) { /** - * @var ColumnSecurity + * @var ColumnSecurity $annotation */ $annotation = $this->reader->getPropertyAnnotation($property, ColumnSecurity::class); @@ -108,7 +146,8 @@ class ElementPermissionListener //Check if user is allowed to edit info, otherwise overwrite the new value // so that nothing is changed in the DB. if ($event->hasChangedField($field_name) && - !$this->security->isGranted($annotation->getEditOperationName(), $element)) { + (!$this->security->isGranted($annotation->getEditOperationName(), $element) + || !$this->security->isGranted($annotation->getReadOperationName(), $element))) { $event->setNewValue($field_name, $event->getOldValue($field_name)); } } diff --git a/templates/Parts/info/_main_infos.html.twig b/templates/Parts/info/_main_infos.html.twig index fdcc99e2..994bc659 100644 --- a/templates/Parts/info/_main_infos.html.twig +++ b/templates/Parts/info/_main_infos.html.twig @@ -11,7 +11,11 @@