mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 01:25:55 +02:00
Check read property on part entity objects.
This commit is contained in:
parent
44bad9029b
commit
3ecbe19fd6
7 changed files with 98 additions and 26 deletions
|
@ -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(
|
||||
'<a href="%s">%s</a>',
|
||||
$this->urlGenerator->listPartsURL($entity),
|
||||
$value
|
||||
);
|
||||
if ($entity->getID() !== null) {
|
||||
return sprintf(
|
||||
'<a href="%s">%s</a>',
|
||||
$this->urlGenerator->listPartsURL($entity),
|
||||
$value
|
||||
);
|
||||
} else {
|
||||
return sprintf('<i>%s</i>', $value);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,11 @@
|
|||
<div class="col-md-9">
|
||||
<h5 class="text-muted pt-2" title="{% trans %}manufacturer.label{% endtrans %}">
|
||||
{% if part.manufacturer %}
|
||||
<a href="{{ part.manufacturer | entityURL('list_parts') }}">{{ part.manufacturer.name}}</a>
|
||||
{% if part.manufacturer.id is not null %}
|
||||
<a href="{{ part.manufacturer | entityURL('list_parts') }}">{{ part.manufacturer.name}}</a>
|
||||
{% else %}
|
||||
{{ part.manufacturer.name }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if part.manufacturerProductUrl %}
|
||||
<small>
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
<ul class="structural_link d-inline">
|
||||
{% for e in entity.pathArray %}
|
||||
<li>
|
||||
{% if link_type is not empty %}
|
||||
{% if link_type is not empty and e.id is not null %}
|
||||
<a href="{{ e | entityURL(link_type) }}">{{ e.name }}</a>
|
||||
{% else %}
|
||||
{{ e.name }}
|
||||
|
@ -99,7 +99,7 @@
|
|||
<ol class="breadcrumb">
|
||||
{% for e in entity.pathArray %}
|
||||
<li class="breadcrumb-item {% if loop.last %}active{% endif %}">
|
||||
{% if link_type is not empty and not loop.last %}
|
||||
{% if link_type is not empty and not loop.last and e.id is not null %}
|
||||
<a href="{{ e | entityURL(link_type) }}">{{ e.name }}</a>
|
||||
{% else %}
|
||||
{{ e.name }}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue