Merge remote-tracking branch 'origin/l10n_master' into l10n_master

This commit is contained in:
Jan Böhmer 2020-04-10 12:37:54 +02:00
commit 8343fffc1c
140 changed files with 3217 additions and 4762 deletions

View file

@ -110,9 +110,12 @@ $(document).on("ajaxUI:start ajaxUI:reload", function() {
}); });
//Register bootstrap select picker //Register bootstrap select picker
$(document).on("ajaxUI:reload", function () { $(document).on("ajaxUI:reload ajaxUI:start", function () {
//@ts-ignore //@ts-ignore
$(".selectpicker").selectpicker(); $(".selectpicker").selectpicker({
dropdownAlignRight: 'auto',
container: '#content',
});
}); });
//Use bootstrap tooltips for the most tooltips //Use bootstrap tooltips for the most tooltips

View file

@ -64,6 +64,7 @@
"phpstan/phpstan": "^0.12.8", "phpstan/phpstan": "^0.12.8",
"phpstan/phpstan-doctrine": "^0.12.9", "phpstan/phpstan-doctrine": "^0.12.9",
"phpstan/phpstan-symfony": "^0.12.4", "phpstan/phpstan-symfony": "^0.12.4",
"psalm/plugin-symfony": "^1.1",
"roave/security-advisories": "dev-master", "roave/security-advisories": "dev-master",
"symfony/debug-pack": "*", "symfony/debug-pack": "*",
"symfony/maker-bundle": "^1.13", "symfony/maker-bundle": "^1.13",

635
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,26 +2,14 @@ doctrine:
orm: orm:
auto_generate_proxy_classes: false auto_generate_proxy_classes: false
metadata_cache_driver: metadata_cache_driver:
type: service type: pool
id: doctrine.system_cache_provider pool: doctrine.system_cache_pool
query_cache_driver: query_cache_driver:
type: service type: pool
id: doctrine.system_cache_provider pool: doctrine.system_cache_pool
result_cache_driver: result_cache_driver:
type: service type: pool
id: doctrine.result_cache_provider pool: doctrine.result_cache_pool
services:
doctrine.result_cache_provider:
class: Symfony\Component\Cache\DoctrineProvider
public: false
arguments:
- '@doctrine.result_cache_pool'
doctrine.system_cache_provider:
class: Symfony\Component\Cache\DoctrineProvider
public: false
arguments:
- '@doctrine.system_cache_pool'
framework: framework:
cache: cache:

View file

@ -4,4 +4,4 @@ webpack_encore:
cache: true cache: true
# Preload in production # Preload in production
preload: false preload: true

View file

@ -8,7 +8,7 @@ webpack_encore:
# crossorigin: 'anonymous' # crossorigin: 'anonymous'
# preload all rendered script and link tags automatically via the http2 Link header # preload all rendered script and link tags automatically via the http2 Link header
#preload: false preload: false
# Throw an exception if the entrypoints.json file is missing or an entry is missing from the data # Throw an exception if the entrypoints.json file is missing or an entry is missing from the data
# strict_mode: false # strict_mode: false

View file

@ -187,6 +187,10 @@ perms: # Here comes a list with all Permission names (they have a perm_[name] co
label: "perm.revert_elements" label: "perm.revert_elements"
bit: 10 bit: 10
alsoSet: ["read", "edit", "create", "delete", "show_history"] alsoSet: ["read", "edit", "create", "delete", "show_history"]
show_private:
label: "perm.attachment_show_private"
bit: 12
alsoSet: ["read"]
parts_order: parts_order:
<<: *PART_ATTRIBUTE <<: *PART_ATTRIBUTE

View file

@ -33,6 +33,8 @@ services:
bind: bind:
bool $demo_mode: '%demo_mode%' bool $demo_mode: '%demo_mode%'
bool $gpdr_compliance : '%gpdr_compliance%' bool $gpdr_compliance : '%gpdr_compliance%'
bool $kernel_debug: '%kernel.debug%'
string $kernel_cache_dir: '%kernel.cache_dir%'
# makes classes in src/ available to be used as services # makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name # this creates a service per class whose id is the fully-qualified class name

View file

@ -1,5 +1,6 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<psalm <psalm
errorLevel="5"
totallyTyped="false" totallyTyped="false"
resolveFromConfigFile="true" resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@ -7,49 +8,49 @@
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
> >
<projectFiles> <projectFiles>
<directory name="src" /> <directory name="src"/>
<ignoreFiles> <ignoreFiles>
<directory name="vendor" /> <directory name="vendor"/>
</ignoreFiles> </ignoreFiles>
</projectFiles> </projectFiles>
<issueHandlers> <issueHandlers>
<LessSpecificReturnType errorLevel="info" /> <LessSpecificReturnType errorLevel="info"/>
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives --> <!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
<DeprecatedMethod errorLevel="info" /> <DeprecatedMethod errorLevel="info"/>
<DeprecatedProperty errorLevel="info" /> <DeprecatedProperty errorLevel="info"/>
<DeprecatedClass errorLevel="info" /> <DeprecatedClass errorLevel="info"/>
<DeprecatedConstant errorLevel="info" /> <DeprecatedConstant errorLevel="info"/>
<DeprecatedFunction errorLevel="info" /> <DeprecatedFunction errorLevel="info"/>
<DeprecatedInterface errorLevel="info" /> <DeprecatedInterface errorLevel="info"/>
<DeprecatedTrait errorLevel="info" /> <DeprecatedTrait errorLevel="info"/>
<InternalMethod errorLevel="info" /> <InternalMethod errorLevel="info"/>
<InternalProperty errorLevel="info" /> <InternalProperty errorLevel="info"/>
<InternalClass errorLevel="info" /> <InternalClass errorLevel="info"/>
<MissingClosureReturnType errorLevel="info" /> <MissingClosureReturnType errorLevel="info"/>
<MissingReturnType errorLevel="info" /> <MissingReturnType errorLevel="info"/>
<MissingPropertyType errorLevel="info" /> <MissingPropertyType errorLevel="info"/>
<InvalidDocblock errorLevel="info" /> <InvalidDocblock errorLevel="info"/>
<MisplacedRequiredParam errorLevel="info" /> <MisplacedRequiredParam errorLevel="info"/>
<PropertyNotSetInConstructor errorLevel="info" /> <PropertyNotSetInConstructor errorLevel="info"/>
<MissingConstructor errorLevel="info" /> <MissingConstructor errorLevel="info"/>
<MissingClosureParamType errorLevel="info" /> <MissingClosureParamType errorLevel="info"/>
<MissingParamType errorLevel="info" /> <MissingParamType errorLevel="info"/>
<RedundantCondition errorLevel="info" /> <RedundantCondition errorLevel="info"/>
<DocblockTypeContradiction errorLevel="info" /> <DocblockTypeContradiction errorLevel="info"/>
<RedundantConditionGivenDocblockType errorLevel="info" /> <RedundantConditionGivenDocblockType errorLevel="info"/>
<UnresolvableInclude errorLevel="info" /> <UnresolvableInclude errorLevel="info"/>
<RawObjectIteration errorLevel="info" /> <RawObjectIteration errorLevel="info"/>
<InvalidStringClass errorLevel="info" /> <InvalidStringClass errorLevel="info"/>
</issueHandlers> </issueHandlers>
</psalm> <plugins><pluginClass class="Psalm\SymfonyPsalmPlugin\Plugin"/></plugins></psalm>

View file

@ -197,7 +197,7 @@ abstract class BaseAdminController extends AbstractController
//We can not use dynamic form events here, because the parent entity list is build from database! //We can not use dynamic form events here, because the parent entity list is build from database!
$form = $this->createForm($this->form_class, $entity, [ $form = $this->createForm($this->form_class, $entity, [
'attachment_class' => $this->attachment_class, 'attachment_class' => $this->attachment_class,
'parameter_class' => $this->parameter_class 'parameter_class' => $this->parameter_class,
]); ]);
} elseif ($form->isSubmitted() && ! $form->isValid()) { } elseif ($form->isSubmitted() && ! $form->isValid()) {
$this->addFlash('error', 'entity.edit_flash.invalid'); $this->addFlash('error', 'entity.edit_flash.invalid');

View file

@ -72,7 +72,7 @@ class FootprintController extends BaseAdminController
* *
* @return \Symfony\Component\HttpFoundation\RedirectResponse * @return \Symfony\Component\HttpFoundation\RedirectResponse
*/ */
public function delete(Request $request, Footprint $entity, StructuralElementRecursionHelper $recursionHelper) public function delete(Request $request, Footprint $entity, StructuralElementRecursionHelper $recursionHelper): \Symfony\Component\HttpFoundation\RedirectResponse
{ {
return $this->_delete($request, $entity, $recursionHelper); return $this->_delete($request, $entity, $recursionHelper);
} }
@ -83,7 +83,7 @@ class FootprintController extends BaseAdminController
* *
* @return Response * @return Response
*/ */
public function edit(Footprint $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null) public function edit(Footprint $entity, Request $request, EntityManagerInterface $em, ?string $timestamp = null): Response
{ {
return $this->_edit($entity, $request, $em, $timestamp); return $this->_edit($entity, $request, $em, $timestamp);
} }

View file

@ -69,6 +69,10 @@ class AttachmentFileController extends AbstractController
{ {
$this->denyAccessUnlessGranted('read', $attachment); $this->denyAccessUnlessGranted('read', $attachment);
if ($attachment->isSecure()) {
$this->denyAccessUnlessGranted('show_private', $attachment);
}
if ($attachment->isExternal()) { if ($attachment->isExternal()) {
throw new RuntimeException('You can not download external attachments!'); throw new RuntimeException('You can not download external attachments!');
} }
@ -97,6 +101,10 @@ class AttachmentFileController extends AbstractController
{ {
$this->denyAccessUnlessGranted('read', $attachment); $this->denyAccessUnlessGranted('read', $attachment);
if ($attachment->isSecure()) {
$this->denyAccessUnlessGranted('show_private', $attachment);
}
if ($attachment->isExternal()) { if ($attachment->isExternal()) {
throw new RuntimeException('You can not download external attachments!'); throw new RuntimeException('You can not download external attachments!');
} }

View file

@ -53,7 +53,6 @@ use App\Services\LogSystem\EventUndoHelper;
use App\Services\LogSystem\TimeTravel; use App\Services\LogSystem\TimeTravel;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Omines\DataTablesBundle\DataTableFactory; use Omines\DataTablesBundle\DataTableFactory;
use phpDocumentor\Reflection\Element;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -100,7 +99,7 @@ class LogController extends AbstractController
/** /**
* @Route("/undo", name="log_undo", methods={"POST"}) * @Route("/undo", name="log_undo", methods={"POST"})
*/ */
public function undoRevertLog(Request $request, EventUndoHelper $eventUndoHelper) public function undoRevertLog(Request $request, EventUndoHelper $eventUndoHelper): \Symfony\Component\HttpFoundation\RedirectResponse
{ {
$mode = EventUndoHelper::MODE_UNDO; $mode = EventUndoHelper::MODE_UNDO;
$id = $request->request->get('undo'); $id = $request->request->get('undo');

View file

@ -44,7 +44,12 @@ namespace App\Controller;
use App\DataTables\LogDataTable; use App\DataTables\LogDataTable;
use App\Entity\Parts\Category; use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot;
use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier;
use App\Entity\PriceInformations\Orderdetail;
use App\Exceptions\AttachmentDownloadException; use App\Exceptions\AttachmentDownloadException;
use App\Form\Part\PartBaseType; use App\Form\Part\PartBaseType;
use App\Services\Attachments\AttachmentManager; use App\Services\Attachments\AttachmentManager;
@ -57,6 +62,7 @@ use App\Services\Parameters\ParameterExtractor;
use App\Services\PricedetailHelper; use App\Services\PricedetailHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Omines\DataTablesBundle\DataTableFactory; use Omines\DataTablesBundle\DataTableFactory;
use Proxies\__CG__\App\Entity\Parts\Manufacturer;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\RedirectResponse;
@ -135,7 +141,7 @@ class PartController extends AbstractController
'pictures' => $this->partPreviewGenerator->getPreviewAttachments($part), 'pictures' => $this->partPreviewGenerator->getPreviewAttachments($part),
'timeTravel' => $timeTravel_timestamp, 'timeTravel' => $timeTravel_timestamp,
'description_params' => $parameterExtractor->extractParameters($part->getDescription()), 'description_params' => $parameterExtractor->extractParameters($part->getDescription()),
'comment_params' => $parameterExtractor->extractParameters($part->getComment()) 'comment_params' => $parameterExtractor->extractParameters($part->getComment()),
] ]
); );
} }
@ -235,13 +241,42 @@ class PartController extends AbstractController
$this->denyAccessUnlessGranted('create', $new_part); $this->denyAccessUnlessGranted('create', $new_part);
$cid = $request->get('cid', 1); $cid = $request->get('category', null);
$category = $cid ? $em->find(Category::class, $cid) : null;
$category = $em->find(Category::class, $cid);
if (null !== $category && null === $new_part->getCategory()) { if (null !== $category && null === $new_part->getCategory()) {
$new_part->setCategory($category); $new_part->setCategory($category);
} }
$fid = $request->get('footprint', null);
$footprint = $fid ? $em->find(Footprint::class, $cid) : null;
if (null !== $footprint && null === $new_part->getFootprint()) {
$new_part->setFootprint($footprint);
}
$mid = $request->get('manufacturer', null);
$manufacturer = $mid ? $em->find(Manufacturer::class, $mid) : null;
if (null !== $manufacturer && null === $new_part->getManufacturer()) {
$new_part->setManufacturer($manufacturer);
}
$store_id = $request->get('storelocation', null);
$storelocation = $store_id ? $em->find(Storelocation::class, $store_id): null;
if (null !== $storelocation && $new_part->getPartLots()->isEmpty()) {
$partLot = new PartLot();
$partLot->setStorageLocation($storelocation);
$partLot->setInstockUnknown(true);
$new_part->addPartLot($partLot);
}
$supplier_id = $request->get('supplier', null);
$supplier = $supplier_id ? $em->find(Supplier::class, $supplier_id): null;
if (null !== $supplier && $new_part->getOrderdetails()->isEmpty()) {
$orderdetail = new Orderdetail();
$orderdetail->setSupplier($supplier);
$new_part->addOrderdetail($orderdetail);
}
$form = $this->createForm(PartBaseType::class, $new_part); $form = $this->createForm(PartBaseType::class, $new_part);
$form->handleRequest($request); $form->handleRequest($request);

View file

@ -62,9 +62,11 @@ class TypeaheadController extends AbstractController
/** /**
* @Route("/builtInResources/search/{query}", name="typeahead_builtInRessources", requirements={"query"= ".+"}) * @Route("/builtInResources/search/{query}", name="typeahead_builtInRessources", requirements={"query"= ".+"})
* *
* @param string $query
* @param BuiltinAttachmentsFinder $finder
* @return JsonResponse * @return JsonResponse
*/ */
public function builtInResources(Request $request, string $query, BuiltinAttachmentsFinder $finder) public function builtInResources(string $query, BuiltinAttachmentsFinder $finder)
{ {
$array = $finder->find($query); $array = $finder->find($query);

View file

@ -43,7 +43,6 @@ declare(strict_types=1);
namespace App\Controller; namespace App\Controller;
use App\Entity\Attachments\UserAttachment; use App\Entity\Attachments\UserAttachment;
use App\Entity\Parameters\PartParameter;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Form\Permissions\PermissionsType; use App\Form\Permissions\PermissionsType;
use App\Form\UserAdminForm; use App\Form\UserAdminForm;
@ -69,7 +68,7 @@ class UserController extends AdminPages\BaseAdminController
protected $route_base = 'user'; protected $route_base = 'user';
protected $attachment_class = UserAttachment::class; protected $attachment_class = UserAttachment::class;
//Just define a value here to prevent error. It is not used. //Just define a value here to prevent error. It is not used.
protected $parameter_class = "not used"; protected $parameter_class = 'not used';
/** /**
* @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="user_edit") * @Route("/{id}/edit/{timestamp}", requirements={"id"="\d+"}, name="user_edit")

View file

@ -43,7 +43,7 @@ declare(strict_types=1);
namespace App\DataFixtures; namespace App\DataFixtures;
use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
class AppFixtures extends Fixture class AppFixtures extends Fixture
{ {

View file

@ -52,7 +52,7 @@ use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Storelocation; use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier; use App\Entity\Parts\Supplier;
use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException; use InvalidArgumentException;

View file

@ -44,7 +44,7 @@ namespace App\DataFixtures;
use App\Entity\UserSystem\Group; use App\Entity\UserSystem\Group;
use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
class GroupFixtures extends Fixture class GroupFixtures extends Fixture
{ {

View file

@ -89,16 +89,16 @@ class PartFixtures extends Fixture
$orderdetail = new Orderdetail(); $orderdetail = new Orderdetail();
$orderdetail->setSupplier($manager->find(Supplier::class, 1)); $orderdetail->setSupplier($manager->find(Supplier::class, 1));
$orderdetail->addPricedetail((new Pricedetail())->setPriceRelatedQuantity(1.0)->setPrice("10.0")); $orderdetail->addPricedetail((new Pricedetail())->setPriceRelatedQuantity(1.0)->setPrice('10.0'));
$orderdetail->addPricedetail((new Pricedetail())->setPriceRelatedQuantity(10.0)->setPrice("15.0")); $orderdetail->addPricedetail((new Pricedetail())->setPriceRelatedQuantity(10.0)->setPrice('15.0'));
$part->addOrderdetail($orderdetail); $part->addOrderdetail($orderdetail);
$orderdetail = new Orderdetail(); $orderdetail = new Orderdetail();
$orderdetail->setSupplierpartnr('BC 547'); $orderdetail->setSupplierpartnr('BC 547');
$orderdetail->setObsolete(true); $orderdetail->setObsolete(true);
$orderdetail->setSupplier($manager->find(Supplier::class, 1)); $orderdetail->setSupplier($manager->find(Supplier::class, 1));
$orderdetail->addPricedetail((new Pricedetail())->setPriceRelatedQuantity(1.0)->setPrice("10.0")); $orderdetail->addPricedetail((new Pricedetail())->setPriceRelatedQuantity(1.0)->setPrice('10.0'));
$orderdetail->addPricedetail((new Pricedetail())->setPriceRelatedQuantity(10.0)->setPrice("15.1")); $orderdetail->addPricedetail((new Pricedetail())->setPriceRelatedQuantity(10.0)->setPrice('15.1'));
$part->addOrderdetail($orderdetail); $part->addOrderdetail($orderdetail);
$attachment = new PartAttachment(); $attachment = new PartAttachment();

View file

@ -44,7 +44,7 @@ namespace App\DataFixtures;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;

View file

@ -98,7 +98,7 @@ class FetchJoinORMAdapter extends ORMAdapter
} }
} }
public function getCount(QueryBuilder $queryBuilder, $identifier) public function getCount(QueryBuilder $queryBuilder, string $identifier)
{ {
$paginator = new Paginator($queryBuilder); $paginator = new Paginator($queryBuilder);

View file

@ -216,7 +216,7 @@ class ORMAdapter extends AbstractAdapter
* *
* @return int * @return int
*/ */
protected function getCount(QueryBuilder $queryBuilder, $identifier) protected function getCount(QueryBuilder $queryBuilder, string $identifier)
{ {
$qb = clone $queryBuilder; $qb = clone $queryBuilder;

View file

@ -44,7 +44,6 @@ namespace App\DataTables\Column;
use App\Entity\Attachments\Attachment; use App\Entity\Attachments\Attachment;
use App\Entity\Base\AbstractDBElement; use App\Entity\Base\AbstractDBElement;
use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Contracts\NamedElementInterface; use App\Entity\Contracts\NamedElementInterface;
use App\Entity\LogSystem\AbstractLogEntry; use App\Entity\LogSystem\AbstractLogEntry;
use App\Entity\Parameters\AbstractParameter; use App\Entity\Parameters\AbstractParameter;
@ -99,7 +98,7 @@ class LogEntryTargetColumn extends AbstractColumn
$tmp = ''; $tmp = '';
//The element is existing //The element is existing
if ($target instanceof NamedElementInterface && !empty($target->getName())) { if ($target instanceof NamedElementInterface && ! empty($target->getName())) {
try { try {
$tmp = sprintf( $tmp = sprintf(
'<a href="%s">%s</a>', '<a href="%s">%s</a>',
@ -126,15 +125,15 @@ class LogEntryTargetColumn extends AbstractColumn
//Add a hint to the associated element if possible //Add a hint to the associated element if possible
if (null !== $target && $this->options['show_associated']) { if (null !== $target && $this->options['show_associated']) {
if ($target instanceof Attachment && $target->getElement() !== null) { if ($target instanceof Attachment && null !== $target->getElement()) {
$on = $target->getElement(); $on = $target->getElement();
} elseif ($target instanceof AbstractParameter && $target->getElement() !== null) { } elseif ($target instanceof AbstractParameter && null !== $target->getElement()) {
$on = $target->getElement(); $on = $target->getElement();
} elseif ($target instanceof PartLot && $target->getPart() !== null) { } elseif ($target instanceof PartLot && null !== $target->getPart()) {
$on = $target->getPart(); $on = $target->getPart();
} elseif ($target instanceof Orderdetail && $target->getPart() !== null) { } elseif ($target instanceof Orderdetail && null !== $target->getPart()) {
$on = $target->getPart(); $on = $target->getPart();
} elseif ($target instanceof Pricedetail && $target->getOrderdetail() !== null && $target->getOrderdetail()->getPart() !== null) { } elseif ($target instanceof Pricedetail && null !== $target->getOrderdetail() && null !== $target->getOrderdetail()->getPart()) {
$on = $target->getOrderdetail()->getPart(); $on = $target->getOrderdetail()->getPart();
} }
@ -146,7 +145,7 @@ class LogEntryTargetColumn extends AbstractColumn
$this->elementTypeNameGenerator->getTypeNameCombination($on, true) $this->elementTypeNameGenerator->getTypeNameCombination($on, true)
); );
} catch (EntityNotSupportedException $exception) { } catch (EntityNotSupportedException $exception) {
$tmp .= ' (' . $this->elementTypeNameGenerator->getTypeNameCombination($target, true) .')'; $tmp .= ' ('.$this->elementTypeNameGenerator->getTypeNameCombination($target, true).')';
} }
} }
} }

View file

@ -49,7 +49,6 @@ class RevertLogColumn extends AbstractColumn
public function render($value, $context) public function render($value, $context)
{ {
$revertable = true;
if ( if (
$context instanceof CollectionElementDeleted $context instanceof CollectionElementDeleted
|| ($context instanceof ElementDeletedLogEntry && $context->hasOldDataInformations()) || ($context instanceof ElementDeletedLogEntry && $context->hasOldDataInformations())

View file

@ -212,7 +212,7 @@ class LogDataTable implements DataTableTypeInterface
$dataTable->add('target', LogEntryTargetColumn::class, [ $dataTable->add('target', LogEntryTargetColumn::class, [
'label' => $this->translator->trans('log.target'), 'label' => $this->translator->trans('log.target'),
'show_associated' => $options['mode'] !== 'element_history', 'show_associated' => 'element_history' !== $options['mode'],
]); ]);
$dataTable->add('extra', LogEntryExtraColumn::class, [ $dataTable->add('extra', LogEntryExtraColumn::class, [

View file

@ -329,8 +329,6 @@ final class PartsDataTable implements DataTableTypeInterface
private function buildCriteria(QueryBuilder $builder, array $options): void private function buildCriteria(QueryBuilder $builder, array $options): void
{ {
$em = $builder->getEntityManager();
if (isset($options['category'])) { if (isset($options['category'])) {
$category = $options['category']; $category = $options['category'];
$list = $this->treeBuilder->typeToNodesList(Category::class, $category); $list = $this->treeBuilder->typeToNodesList(Category::class, $category);

View file

@ -40,6 +40,7 @@ class AttachmentType extends AbstractStructuralDBElement
{ {
/** /**
* @ORM\OneToMany(targetEntity="AttachmentType", mappedBy="parent", cascade={"persist"}) * @ORM\OneToMany(targetEntity="AttachmentType", mappedBy="parent", cascade={"persist"})
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -56,13 +57,14 @@ class AttachmentType extends AbstractStructuralDBElement
*/ */
protected $filetype_filter = ''; protected $filetype_filter = '';
/** /**
* @var Collection|AttachmentTypeAttachment[] * @var Collection<int, AttachmentTypeAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\AttachmentTypeAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\AttachmentTypeAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected $attachments;
/** @var AttachmentTypeParameter[] /** @var Collection<int, AttachmentTypeParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\AttachmentTypeParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\AttachmentTypeParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
@ -70,7 +72,7 @@ class AttachmentType extends AbstractStructuralDBElement
protected $parameters; protected $parameters;
/** /**
* @var Collection|Attachment[] * @var Collection<int, Attachment>
* @ORM\OneToMany(targetEntity="Attachment", mappedBy="attachment_type") * @ORM\OneToMany(targetEntity="Attachment", mappedBy="attachment_type")
*/ */
protected $attachments_with_type; protected $attachments_with_type;

View file

@ -43,11 +43,13 @@ declare(strict_types=1);
namespace App\Entity\Attachments; namespace App\Entity\Attachments;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to an attachmentType element. * A attachment attached to an attachmentType element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class AttachmentTypeAttachment extends Attachment class AttachmentTypeAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\Parts\Category; use App\Entity\Parts\Category;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a category element. * A attachment attached to a category element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class CategoryAttachment extends Attachment class CategoryAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Currency;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a currency element. * A attachment attached to a currency element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class CurrencyAttachment extends Attachment class CurrencyAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\Devices\Device; use App\Entity\Devices\Device;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a device element. * A attachment attached to a device element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class DeviceAttachment extends Attachment class DeviceAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\Parts\Footprint; use App\Entity\Parts\Footprint;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a footprint element. * A attachment attached to a footprint element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class FootprintAttachment extends Attachment class FootprintAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\UserSystem\Group; use App\Entity\UserSystem\Group;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a Group element. * A attachment attached to a Group element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class GroupAttachment extends Attachment class GroupAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Manufacturer;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a manufacturer element. * A attachment attached to a manufacturer element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class ManufacturerAttachment extends Attachment class ManufacturerAttachment extends Attachment
{ {

View file

@ -45,11 +45,13 @@ namespace App\Entity\Attachments;
use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit; use App\Entity\Parts\MeasurementUnit;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a measurement unit element. * A attachment attached to a measurement unit element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class MeasurementUnitAttachment extends Attachment class MeasurementUnitAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a part element. * A attachment attached to a part element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class PartAttachment extends Attachment class PartAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\Parts\Storelocation; use App\Entity\Parts\Storelocation;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a measurement unit element. * A attachment attached to a measurement unit element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class StorelocationAttachment extends Attachment class StorelocationAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\Parts\Supplier; use App\Entity\Parts\Supplier;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a supplier element. * A attachment attached to a supplier element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class SupplierAttachment extends Attachment class SupplierAttachment extends Attachment
{ {

View file

@ -44,11 +44,13 @@ namespace App\Entity\Attachments;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/** /**
* A attachment attached to a user element. * A attachment attached to a user element.
* *
* @ORM\Entity() * @ORM\Entity()
* @UniqueEntity({"name", "attachment_type", "element"})
*/ */
class UserAttachment extends Attachment class UserAttachment extends Attachment
{ {

View file

@ -254,6 +254,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
* Get all sub elements of this element. * Get all sub elements of this element.
* *
* @return Collection<static>|iterable all subelements as an array of objects (sorted by their full path) * @return Collection<static>|iterable all subelements as an array of objects (sorted by their full path)
* @psalm-return Collection<int, static>
*/ */
public function getSubelements(): iterable public function getSubelements(): iterable
{ {
@ -262,6 +263,7 @@ abstract class AbstractStructuralDBElement extends AttachmentContainingDBElement
/** /**
* @return Collection<static>|iterable * @return Collection<static>|iterable
* @psalm-return Collection<int, static>
*/ */
public function getChildren(): iterable public function getChildren(): iterable
{ {

View file

@ -67,6 +67,7 @@ class Device extends AbstractPartsContainingDBElement
{ {
/** /**
* @ORM\OneToMany(targetEntity="Device", mappedBy="parent") * @ORM\OneToMany(targetEntity="Device", mappedBy="parent")
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -93,12 +94,13 @@ class Device extends AbstractPartsContainingDBElement
*/ */
protected $order_only_missing_parts = false; protected $order_only_missing_parts = false;
/** /**
* @var Collection|DeviceAttachment[] * @var Collection<int, DeviceAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\DeviceAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\DeviceAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $attachments; protected $attachments;
/** @var DeviceParameter[] /** @var Collection<int, DeviceParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\DeviceParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\DeviceParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
*/ */

View file

@ -84,11 +84,7 @@ class ElementEditedLogEntry extends AbstractLogEntry implements TimeTravelInterf
return array_keys($this->getOldData()); return array_keys($this->getOldData());
} }
if (isset($this->extra['f'])) { return $this->extra['f'] ?? [];
return $this->extra['f'];
}
return [];
} }
/** /**

View file

@ -28,7 +28,6 @@ use App\Entity\Base\AbstractNamedDBElement;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException; use InvalidArgumentException;
use LogicException; use LogicException;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
/** /**
@ -121,16 +120,6 @@ abstract class AbstractParameter extends AbstractNamedDBElement
} }
} }
/**
* Returns the name of the specification (e.g. "Collector-Base Voltage").
*
* @return string
*/
public function getName(): string
{
return $this->name;
}
/** /**
* Returns the element this parameter belongs to. * Returns the element this parameter belongs to.
* *
@ -213,7 +202,8 @@ abstract class AbstractParameter extends AbstractNamedDBElement
} }
/** /**
* Returns the name of the group this parameter is associated to (e.g. Technical Parameters) * Returns the name of the group this parameter is associated to (e.g. Technical Parameters).
*
* @return string * @return string
*/ */
public function getGroup(): string public function getGroup(): string
@ -223,12 +213,13 @@ abstract class AbstractParameter extends AbstractNamedDBElement
/** /**
* Sets the name of the group this parameter is associated to. * Sets the name of the group this parameter is associated to.
* @param string $group *
* @return $this * @return $this
*/ */
public function setGroup(string $group): self public function setGroup(string $group): self
{ {
$this->group = $group; $this->group = $group;
return $this; return $this;
} }

View file

@ -31,17 +31,19 @@ trait ParametersTrait
/** /**
* Mapping done in subclasses. * Mapping done in subclasses.
* *
* @var AbstractParameter[]|Collection * @var Collection<int, AbstractParameter>
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $parameters; protected $parameters;
/** /**
* Return all associated specifications. * Return all associated specifications.
* *
* @return AbstractParameter[]|Collection * @return Collection
*
* @psalm-return Collection<int, PartParameter>
*/ */
public function getParameters(): Collection public function getParameters(): \Doctrine\Common\Collections\Collection
{ {
return $this->parameters; return $this->parameters;
} }
@ -73,6 +75,7 @@ trait ParametersTrait
foreach ($this->parameters as $parameter) { foreach ($this->parameters as $parameter) {
$tmp[$parameter->getGroup()][] = $parameter; $tmp[$parameter->getGroup()][] = $parameter;
} }
return $tmp; return $tmp;
} }
} }

View file

@ -39,6 +39,7 @@ class Category extends AbstractPartsContainingDBElement
{ {
/** /**
* @ORM\OneToMany(targetEntity="Category", mappedBy="parent") * @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -101,13 +102,14 @@ class Category extends AbstractPartsContainingDBElement
*/ */
protected $default_comment = ''; protected $default_comment = '';
/** /**
* @var Collection|CategoryAttachment[] * @var Collection<int, CategoryAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\CategoryAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\CategoryAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected $attachments;
/** @var CategoryParameter[] /** @var Collection<int, CategoryParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\CategoryParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\CategoryParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()

View file

@ -73,6 +73,7 @@ class Footprint extends AbstractPartsContainingDBElement
/** /**
* @ORM\OneToMany(targetEntity="Footprint", mappedBy="parent") * @ORM\OneToMany(targetEntity="Footprint", mappedBy="parent")
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -81,8 +82,9 @@ class Footprint extends AbstractPartsContainingDBElement
*/ */
protected $parts; protected $parts;
/** /**
* @var Collection|FootprintAttachment[] * @var Collection<int, FootprintAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\FootprintAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\FootprintAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected $attachments;
@ -94,9 +96,9 @@ class Footprint extends AbstractPartsContainingDBElement
*/ */
protected $footprint_3d; protected $footprint_3d;
/** @var FootprintParameter[] /** @var Collection<int, FootprintParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\FootprintParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\FootprintParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})@ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $parameters; protected $parameters;

View file

@ -73,6 +73,7 @@ class Manufacturer extends AbstractCompany
/** /**
* @ORM\OneToMany(targetEntity="Manufacturer", mappedBy="parent") * @ORM\OneToMany(targetEntity="Manufacturer", mappedBy="parent")
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -81,13 +82,14 @@ class Manufacturer extends AbstractCompany
*/ */
protected $parts; protected $parts;
/** /**
* @var Collection|ManufacturerAttachment[] * @var Collection<int, ManufacturerAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\ManufacturerAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\ManufacturerAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected $attachments;
/** @var ManufacturerParameter[] /** @var Collection<int, ManufacturerParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\ManufacturerParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\ManufacturerParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()

View file

@ -84,6 +84,7 @@ class MeasurementUnit extends AbstractPartsContainingDBElement
/** /**
* @ORM\OneToMany(targetEntity="MeasurementUnit", mappedBy="parent", cascade={"persist"}) * @ORM\OneToMany(targetEntity="MeasurementUnit", mappedBy="parent", cascade={"persist"})
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -98,13 +99,14 @@ class MeasurementUnit extends AbstractPartsContainingDBElement
*/ */
protected $parts; protected $parts;
/** /**
* @var Collection|MeasurementUnitAttachment[] * @var Collection<int, MeasurementUnitAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\MeasurementUnitAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\MeasurementUnitAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected $attachments;
/** @var MeasurementUnitParameter[] /** @var Collection<int, MeasurementUnitParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\MeasurementUnitParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\MeasurementUnitParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()

View file

@ -52,6 +52,7 @@ namespace App\Entity\Parts;
use App\Entity\Attachments\Attachment; use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\AttachmentContainingDBElement;
use App\Entity\Attachments\PartAttachment;
use App\Entity\Devices\Device; use App\Entity\Devices\Device;
use App\Entity\Parameters\ParametersTrait; use App\Entity\Parameters\ParametersTrait;
use App\Entity\Parameters\PartParameter; use App\Entity\Parameters\PartParameter;
@ -63,6 +64,7 @@ use App\Entity\Parts\PartTraits\OrderTrait;
use App\Security\Annotations\ColumnSecurity; use App\Security\Annotations\ColumnSecurity;
use DateTime; use DateTime;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
@ -90,11 +92,10 @@ class Part extends AttachmentContainingDBElement
*/ */
protected $devices = []; protected $devices = [];
/** @var PartParameter[] /** @var Collection<int, PartParameter>
* @Assert\Valid() * @Assert\Valid()
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\PartParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\PartParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
*/ */
protected $parameters; protected $parameters;
@ -117,8 +118,10 @@ class Part extends AttachmentContainingDBElement
protected $name = ''; protected $name = '';
/** /**
* @var Collection<int, PartAttachment>
* @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=true)
* @ColumnSecurity(type="collection", prefix="attachments") * @ColumnSecurity(type="collection", prefix="attachments")
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected $attachments;

View file

@ -303,8 +303,20 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
return (float) $this->amount; return (float) $this->amount;
} }
public function setAmount(float $new_amount): self /**
* Sets the amount of parts in the part lot.
* If null is passed, amount will be set to unknown.
* @param float|null $new_amount
* @return $this
*/
public function setAmount(?float $new_amount): self
{ {
//Treat null like unknown amount
if ($new_amount === null) {
$this->instock_unknown = true;
$new_amount = 0.0;
}
$this->amount = $new_amount; $this->amount = $new_amount;
return $this; return $this;
@ -328,9 +340,6 @@ class PartLot extends AbstractDBElement implements TimeStampableInterface, Named
return $this; return $this;
} }
/**
* @inheritDoc
*/
public function getName(): string public function getName(): string
{ {
return $this->description; return $this->description;

View file

@ -57,6 +57,7 @@ trait InstockTrait
* @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="PartLot", mappedBy="part", cascade={"persist", "remove"}, orphanRemoval=true)
* @Assert\Valid() * @Assert\Valid()
* @ColumnSecurity(type="collection", prefix="lots") * @ColumnSecurity(type="collection", prefix="lots")
* @ORM\OrderBy({"amount" = "DESC"})
*/ */
protected $partLots; protected $partLots;

View file

@ -44,6 +44,7 @@ namespace App\Entity\Parts\PartTraits;
use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Orderdetail;
use App\Security\Annotations\ColumnSecurity; use App\Security\Annotations\ColumnSecurity;
use Doctrine\ORM\Mapping as ORM;
use function count; use function count;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
@ -57,6 +58,7 @@ trait OrderTrait
* @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=true)
* @Assert\Valid() * @Assert\Valid()
* @ColumnSecurity(prefix="orderdetails", type="collection") * @ColumnSecurity(prefix="orderdetails", type="collection")
* @ORM\OrderBy({"supplierpartnr" = "ASC"})
*/ */
protected $orderdetails; protected $orderdetails;

View file

@ -67,6 +67,7 @@ class Storelocation extends AbstractPartsContainingDBElement
{ {
/** /**
* @ORM\OneToMany(targetEntity="Storelocation", mappedBy="parent") * @ORM\OneToMany(targetEntity="Storelocation", mappedBy="parent")
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -92,7 +93,7 @@ class Storelocation extends AbstractPartsContainingDBElement
*/ */
protected $parts; protected $parts;
/** @var StorelocationParameter[] /** @var Collection<int, StorelocationParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\StorelocationParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\StorelocationParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
@ -117,7 +118,7 @@ class Storelocation extends AbstractPartsContainingDBElement
*/ */
protected $limit_to_existing_parts = false; protected $limit_to_existing_parts = false;
/** /**
* @var Collection|StorelocationAttachment[] * @var Collection<int, StorelocationAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\StorelocationAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\StorelocationAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @Assert\Valid() * @Assert\Valid()
*/ */

View file

@ -69,6 +69,7 @@ class Supplier extends AbstractCompany
{ {
/** /**
* @ORM\OneToMany(targetEntity="Supplier", mappedBy="parent") * @ORM\OneToMany(targetEntity="Supplier", mappedBy="parent")
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -107,14 +108,16 @@ class Supplier extends AbstractCompany
* ) * )
*/ */
protected $parts; protected $parts;
/** /**
* @var Collection|SupplierAttachment[] * @var Collection<int, SupplierAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\SupplierAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\SupplierAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected $attachments;
/** @var SupplierParameter[] /** @var Collection<int, SupplierParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\SupplierParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\SupplierParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()

View file

@ -78,6 +78,7 @@ class Currency extends AbstractStructuralDBElement
/** /**
* @ORM\OneToMany(targetEntity="Currency", mappedBy="parent", cascade={"persist"}) * @ORM\OneToMany(targetEntity="Currency", mappedBy="parent", cascade={"persist"})
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -88,13 +89,14 @@ class Currency extends AbstractStructuralDBElement
protected $parent; protected $parent;
/** /**
* @var Collection|CurrencyAttachment[] * @var Collection<int, CurrencyAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\CurrencyAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\CurrencyAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected $attachments;
/** @var CurrencyParameter[] /** @var Collection<int, CurrencyParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\CurrencyParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\CurrencyParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()

View file

@ -59,6 +59,7 @@ use App\Entity\Parts\Supplier;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\Constraints as Assert;
/** /**
@ -67,6 +68,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\Table("`orderdetails`") * @ORM\Table("`orderdetails`")
* @ORM\Entity() * @ORM\Entity()
* @ORM\HasLifecycleCallbacks() * @ORM\HasLifecycleCallbacks()
* @UniqueEntity({"supplierpartnr", "supplier", "part"})
*/ */
class Orderdetail extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface class Orderdetail extends AbstractDBElement implements TimeStampableInterface, NamedElementInterface
{ {
@ -361,9 +363,6 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N
return $this; return $this;
} }
/**
* @inheritDoc
*/
public function getName(): string public function getName(): string
{ {
return $this->getSupplierPartNr(); return $this->getSupplierPartNr();

View file

@ -64,7 +64,7 @@ use Symfony\Component\Validator\Constraints as Assert;
* @ORM\Entity() * @ORM\Entity()
* @ORM\Table("`pricedetails`") * @ORM\Table("`pricedetails`")
* @ORM\HasLifecycleCallbacks() * @ORM\HasLifecycleCallbacks()
* @UniqueEntity(fields={"orderdetail", "min_discount_quantity"}) * @UniqueEntity(fields={"min_discount_quantity", "orderdetail"})
*/ */
class Pricedetail extends AbstractDBElement implements TimeStampableInterface class Pricedetail extends AbstractDBElement implements TimeStampableInterface
{ {
@ -136,11 +136,11 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface
*********************************************************************************/ *********************************************************************************/
/** /**
* Get the orderdetail to which this pricedetail belongs to this pricedetails. * Get the orderdetail to which this pricedetail belongs to this pricedetails.
* *
* @return Orderdetail the orderdetail this price belongs to * @return Orderdetail|null the orderdetail this price belongs to
*/ */
public function getOrderdetail(): Orderdetail public function getOrderdetail(): ?Orderdetail
{ {
return $this->orderdetail; return $this->orderdetail;
} }
@ -157,17 +157,20 @@ class Pricedetail extends AbstractDBElement implements TimeStampableInterface
} }
/** /**
* Get the price for a single unit in the currency associated with this price detail. * Get the price for a single unit in the currency associated with this price detail.
*
* @param float|string $multiplier The returned price (float or string) will be multiplied
* with this multiplier.
*
* You will get the price for $multiplier parts. If you want the price which is stored
* in the database, you have to pass the "price_related_quantity" count as $multiplier.
* *
* @param float|string $multiplier The returned price (float or string) will be multiplied * @param float|string $multiplier The returned price (float or string) will be multiplied
* with this multiplier. * with this multiplier.
* *
* You will get the price for $multiplier parts. If you want the price which is stored * @return null|string the price as a bcmath string
* in the database, you have to pass the "price_related_quantity" count as $multiplier.
*
* @return string the price as a bcmath string
*/ */
public function getPricePerUnit($multiplier = 1.0): string public function getPricePerUnit($multiplier = 1.0): ?string
{ {
$multiplier = (string) $multiplier; $multiplier = (string) $multiplier;
$tmp = bcmul($this->price, $multiplier, static::PRICE_PRECISION); $tmp = bcmul($this->price, $multiplier, static::PRICE_PRECISION);

View file

@ -61,6 +61,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
{ {
/** /**
* @ORM\OneToMany(targetEntity="Group", mappedBy="parent") * @ORM\OneToMany(targetEntity="Group", mappedBy="parent")
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $children; protected $children;
@ -81,8 +82,9 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
*/ */
protected $enforce2FA = false; protected $enforce2FA = false;
/** /**
* @var Collection|GroupAttachment[] * @var Collection<int, GroupAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\ManufacturerAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\ManufacturerAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()
*/ */
protected $attachments; protected $attachments;
@ -93,7 +95,7 @@ class Group extends AbstractStructuralDBElement implements HasPermissionsInterfa
*/ */
protected $permissions; protected $permissions;
/** @var GroupParameter[] /** @var Collection<int, GroupParameter>
* @ORM\OneToMany(targetEntity="App\Entity\Parameters\GroupParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Parameters\GroupParameter", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"}) * @ORM\OrderBy({"group" = "ASC" ,"name" = "ASC"})
* @Assert\Valid() * @Assert\Valid()

View file

@ -53,7 +53,6 @@ namespace App\Entity\UserSystem;
use App\Entity\Attachments\AttachmentContainingDBElement; use App\Entity\Attachments\AttachmentContainingDBElement;
use App\Entity\Attachments\UserAttachment; use App\Entity\Attachments\UserAttachment;
use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Base\MasterAttachmentTrait;
use App\Entity\PriceInformations\Currency; use App\Entity\PriceInformations\Currency;
use App\Security\Interfaces\HasPermissionsInterface; use App\Security\Interfaces\HasPermissionsInterface;
use App\Validator\Constraints\Selectable; use App\Validator\Constraints\Selectable;
@ -114,7 +113,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
* @var string|null The hash of a token the user must provide when he wants to reset his password. * @var string|null The hash of a token the user must provide when he wants to reset his password.
* @ORM\Column(type="string", nullable=true) * @ORM\Column(type="string", nullable=true)
*/ */
protected $pw_reset_token = null; protected $pw_reset_token;
/** /**
* @ORM\Column(type="text", name="config_instock_comment_a") * @ORM\Column(type="text", name="config_instock_comment_a")
@ -228,8 +227,9 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
protected $settings = []; protected $settings = [];
/** /**
* @var Collection|UserAttachment[] * @var Collection<int, UserAttachment>
* @ORM\OneToMany(targetEntity="App\Entity\Attachments\UserAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\Attachments\UserAttachment", mappedBy="element", cascade={"persist", "remove"}, orphanRemoval=true)
* @ORM\OrderBy({"name" = "ASC"})
*/ */
protected $attachments; protected $attachments;
@ -238,7 +238,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
*/ */
protected $backupCodesGenerationDate; protected $backupCodesGenerationDate;
/** @var Collection<TwoFactorKeyInterface> /** @var Collection<int, TwoFactorKeyInterface>
* @ORM\OneToMany(targetEntity="App\Entity\UserSystem\U2FKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true) * @ORM\OneToMany(targetEntity="App\Entity\UserSystem\U2FKey", mappedBy="user", cascade={"REMOVE"}, orphanRemoval=true)
*/ */
protected $u2fKeys; protected $u2fKeys;
@ -252,7 +252,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
* @ORM\JoinColumn(name="currency_id", referencedColumnName="id") * @ORM\JoinColumn(name="currency_id", referencedColumnName="id")
* @Selectable() * @Selectable()
*/ */
protected $currency = null; protected $currency;
/** @var PermissionsEmbed /** @var PermissionsEmbed
* @ORM\Embedded(class="PermissionsEmbed", columnPrefix="perms_") * @ORM\Embedded(class="PermissionsEmbed", columnPrefix="perms_")
@ -264,7 +264,7 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
* @var DateTime The time until the password reset token is valid. * @var DateTime The time until the password reset token is valid.
* @ORM\Column(type="datetime", nullable=true) * @ORM\Column(type="datetime", nullable=true)
*/ */
protected $pw_reset_expires = null; protected $pw_reset_expires;
public function __construct() public function __construct()
{ {
@ -863,9 +863,11 @@ class User extends AttachmentContainingDBElement implements UserInterface, HasPe
} }
/** /**
* Get all U2F Keys that are associated with this user. * Get all U2F Keys that are associated with this user.
* *
* @return Collection<TwoFactorKeyInterface> * @return Collection
*
* @psalm-return Collection<int, TwoFactorKeyInterface>
*/ */
public function getU2FKeys(): Collection public function getU2FKeys(): Collection
{ {

View file

@ -77,12 +77,21 @@ class AttachmentDeleteListener
public function preUpdateHandler(Attachment $attachment, PreUpdateEventArgs $event): void public function preUpdateHandler(Attachment $attachment, PreUpdateEventArgs $event): void
{ {
if ($event->hasChangedField('path')) { if ($event->hasChangedField('path')) {
$old_path = $event->getOldValue('path');
//Dont delete file if the attachment uses a builtin ressource: //Dont delete file if the attachment uses a builtin ressource:
if (Attachment::checkIfBuiltin($event->getOldValue('path'))) { if (Attachment::checkIfBuiltin($old_path)) {
return; return;
} }
$file = new SplFileInfo($this->pathResolver->placeholderToRealPath($event->getOldValue('path'))); $real_path = $this->pathResolver->placeholderToRealPath($old_path);
//If the attachment does not point to a valid file, ignore it!
if ($real_path === null) {
return;
}
$file = new SplFileInfo($real_path);
$this->attachmentReverseSearch->deleteIfNotUsed($file); $this->attachmentReverseSearch->deleteIfNotUsed($file);
} }
} }

View file

@ -60,7 +60,7 @@ class EventLoggerSubscriber implements EventSubscriber
Orderdetail::class => ['part'], Orderdetail::class => ['part'],
Pricedetail::class => ['orderdetail'], Pricedetail::class => ['orderdetail'],
Attachment::class => ['element'], Attachment::class => ['element'],
AbstractParameter::class => ['element'] AbstractParameter::class => ['element'],
]; ];
protected const MAX_STRING_LENGTH = 2000; protected const MAX_STRING_LENGTH = 2000;
@ -163,7 +163,7 @@ class EventLoggerSubscriber implements EventSubscriber
*/ */
public function hasFieldRestrictions(AbstractDBElement $element): bool public function hasFieldRestrictions(AbstractDBElement $element): bool
{ {
foreach (static::FIELD_BLACKLIST as $class => $blacklist) { foreach (array_keys(static::FIELD_BLACKLIST) as $class) {
if (is_a($element, $class)) { if (is_a($element, $class)) {
return true; return true;
} }
@ -283,7 +283,7 @@ class EventLoggerSubscriber implements EventSubscriber
}, ARRAY_FILTER_USE_BOTH); }, ARRAY_FILTER_USE_BOTH);
} }
protected function saveChangeSet(AbstractDBElement $entity, AbstractLogEntry $logEntry, EntityManagerInterface $em, $element_deleted = false): void protected function saveChangeSet(AbstractDBElement $entity, AbstractLogEntry $logEntry, EntityManagerInterface $em, bool $element_deleted = false): void
{ {
$uow = $em->getUnitOfWork(); $uow = $em->getUnitOfWork();

View file

@ -53,8 +53,6 @@ use Symfony\Component\Security\Core\Security;
final class LogoutOnDisabledUserListener implements EventSubscriberInterface final class LogoutOnDisabledUserListener implements EventSubscriberInterface
{ {
private $security; private $security;
private $translator;
private $flashBag;
private $urlGenerator; private $urlGenerator;
public function __construct(Security $security, UrlGeneratorInterface $urlGenerator) public function __construct(Security $security, UrlGeneratorInterface $urlGenerator)

View file

@ -45,14 +45,15 @@ namespace App\EventSubscriber;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent; use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
final class SymfonyDebugToolbarSubscriber implements EventSubscriberInterface final class SymfonyDebugToolbarSubscriber implements EventSubscriberInterface
{ {
private $kernel; private $kernel_debug;
public function __construct(ContainerInterface $kernel) public function __construct(bool $kernel_debug)
{ {
$this->kernel = $kernel; $this->kernel_debug = $kernel_debug;
} }
/** /**
@ -78,9 +79,9 @@ final class SymfonyDebugToolbarSubscriber implements EventSubscriberInterface
return ['kernel.response' => 'onKernelResponse']; return ['kernel.response' => 'onKernelResponse'];
} }
public function onKernelResponse(FilterResponseEvent $event): void public function onKernelResponse(ResponseEvent $event): void
{ {
if (! $this->kernel->getParameter('kernel.debug')) { if (! $this->kernel_debug) {
return; return;
} }

View file

@ -45,7 +45,6 @@ namespace App\Form\AdminPages;
use App\Entity\Attachments\Attachment; use App\Entity\Attachments\Attachment;
use App\Entity\Base\AbstractNamedDBElement; use App\Entity\Base\AbstractNamedDBElement;
use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\Base\AbstractStructuralDBElement;
use App\Entity\Parameters\PartParameter;
use App\Form\AttachmentFormType; use App\Form\AttachmentFormType;
use App\Form\ParameterType; use App\Form\ParameterType;
use App\Form\Type\MasterPictureAttachmentType; use App\Form\Type\MasterPictureAttachmentType;
@ -133,6 +132,7 @@ class BaseEntityAdminForm extends AbstractType
'allow_add' => true, 'allow_add' => true,
'allow_delete' => true, 'allow_delete' => true,
'label' => false, 'label' => false,
'reindex_enable' => true,
'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity), 'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
'entry_options' => [ 'entry_options' => [
'data_class' => $options['attachment_class'], 'data_class' => $options['attachment_class'],
@ -159,6 +159,7 @@ class BaseEntityAdminForm extends AbstractType
'allow_add' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity), 'allow_add' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
'allow_delete' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity), 'allow_delete' => $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity), 'disabled' => ! $this->security->isGranted($is_new ? 'create' : 'edit', $entity),
'reindex_enable' => true,
'label' => false, 'label' => false,
'by_reference' => false, 'by_reference' => false,
'prototype_data' => new $options['parameter_class'](), 'prototype_data' => new $options['parameter_class'](),

View file

@ -57,6 +57,7 @@ use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Constraints\File; use Symfony\Component\Validator\Constraints\File;
use Symfony\Component\Validator\Constraints\Url; use Symfony\Component\Validator\Constraints\Url;
@ -65,13 +66,15 @@ class AttachmentFormType extends AbstractType
protected $attachment_helper; protected $attachment_helper;
protected $urlGenerator; protected $urlGenerator;
protected $allow_attachments_download; protected $allow_attachments_download;
protected $security;
public function __construct(AttachmentManager $attachmentHelper, public function __construct(AttachmentManager $attachmentHelper,
UrlGeneratorInterface $urlGenerator, bool $allow_attachments_downloads) UrlGeneratorInterface $urlGenerator, Security $security, bool $allow_attachments_downloads)
{ {
$this->attachment_helper = $attachmentHelper; $this->attachment_helper = $attachmentHelper;
$this->urlGenerator = $urlGenerator; $this->urlGenerator = $urlGenerator;
$this->allow_attachments_download = $allow_attachments_downloads; $this->allow_attachments_download = $allow_attachments_downloads;
$this->security = $security;
} }
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
@ -103,6 +106,7 @@ class AttachmentFormType extends AbstractType
'required' => false, 'required' => false,
'label' => 'attachment.edit.secure_file', 'label' => 'attachment.edit.secure_file',
'mapped' => false, 'mapped' => false,
'disabled' => !$this->security->isGranted('@parts_attachments.show_private'),
'attr' => [ 'attr' => [
'class' => 'form-control-sm', 'class' => 'form-control-sm',
], ],

View file

@ -0,0 +1,120 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 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/>.
*/
namespace App\Form;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormConfigBuilder;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\Options;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
/**
* Perform a reindexing on CollectionType elements, by assigning the database id as index.
* This prevents issues when the collection that is edited uses a OrderBy annotation and therefore the direction of the
* elements can change during requests.
* Must me enabled by setting reindex_enable to true in Type options.
* @package App\Form
*/
class CollectionTypeExtension extends AbstractTypeExtension
{
protected $propertyAccess;
public function __construct(PropertyAccessorInterface $propertyAccess)
{
$this->propertyAccess = $propertyAccess;
}
public static function getExtendedTypes(): iterable
{
return [CollectionType::class];
}
public function configureOptions(OptionsResolver $resolver)
{
/*$resolver->setDefault('error_mapping', function (Options $options) {
$options->
});*/
$resolver->setDefaults([
'reindex_enable' => false,
'reindex_prefix' => 'db_',
'reindex_path' => 'id',
]);
$resolver->setAllowedTypes('reindex_enable', 'bool');
$resolver->setAllowedTypes('reindex_prefix', 'string');
$resolver->setAllowedTypes('reindex_path', 'string');
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($options) {
$data = $event->getData();
$config = $event->getForm()->getConfig();
//If enabled do a reindexing of the collection
if ($options['reindex_enable'] && $data instanceof Collection) {
$reindexed_data = new ArrayCollection();
$error_mapping = [];
foreach ($data->toArray() as $key => $item) {
$index = $options['reindex_prefix'] . $this->propertyAccess->getValue($item, $options['reindex_path']);
$error_mapping['[' . $key . ']'] = $index;
$reindexed_data->set($index, $item);
}
$event->setData($reindexed_data);
//Add error mapping, so that validator error are mapped correctly to the new index fields
if ($config instanceof FormBuilder && empty($config->getOption('error_mapping'))) {
$this->setOption($config, 'error_mapping', $error_mapping);
}
}
}, 100); //We need to have a higher priority then the PRE_SET_DATA listener on CollectionType
}
/**
* Set the option of the form.
* This a bit hacky cause we access private properties....
* @param $builder
* @param string $option
* @param $value
* @throws \ReflectionException
*/
public function setOption(FormBuilder $builder, string $option, $value): void
{
//We have to use FormConfigBuilder::class here, because options is private and not available in sub classes
$reflection = new \ReflectionClass(FormConfigBuilder::class);
$property = $reflection->getProperty('options');
$property->setAccessible(true);
$tmp = $property->getValue($builder);
$tmp[$option] = $value;
$property->setValue($builder, $tmp);
$property->setAccessible(false);
}
}

View file

@ -113,14 +113,14 @@ class ParameterType extends AbstractType
'attr' => [ 'attr' => [
'placeholder' => 'parameter.group.placeholder', 'placeholder' => 'parameter.group.placeholder',
'class' => 'form-control-sm', 'class' => 'form-control-sm',
] ],
]); ]);
} }
public function configureOptions(OptionsResolver $resolver): void public function configureOptions(OptionsResolver $resolver): void
{ {
$resolver->setDefaults([ $resolver->setDefaults([
'data_class' => AbstractParameter::class, 'data_class' => AbstractParameter::class,
]); ]);
} }
} }

View file

@ -69,9 +69,6 @@ class OrderdetailType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options): void public function buildForm(FormBuilderInterface $builder, array $options): void
{ {
/** @var Orderdetail $orderdetail */
$orderdetail = $builder->getData();
$builder->add('supplierpartnr', TextType::class, [ $builder->add('supplierpartnr', TextType::class, [
'label' => 'orderdetails.edit.supplierpartnr', 'label' => 'orderdetails.edit.supplierpartnr',
'attr' => [ 'attr' => [
@ -117,6 +114,7 @@ class OrderdetailType extends AbstractType
'allow_add' => $this->security->isGranted('@parts_prices.create'), 'allow_add' => $this->security->isGranted('@parts_prices.create'),
'allow_delete' => $this->security->isGranted('@parts_prices.delete'), 'allow_delete' => $this->security->isGranted('@parts_prices.delete'),
'label' => false, 'label' => false,
'reindex_enable' => true,
'prototype_data' => $dummy_pricedetail, 'prototype_data' => $dummy_pricedetail,
'by_reference' => false, 'by_reference' => false,
'entry_options' => [ 'entry_options' => [

View file

@ -52,7 +52,6 @@ use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Entity\PriceInformations\Orderdetail; use App\Entity\PriceInformations\Orderdetail;
use App\Form\AttachmentFormType; use App\Form\AttachmentFormType;
use App\Form\ParameterGroupType;
use App\Form\ParameterType; use App\Form\ParameterType;
use App\Form\Type\MasterPictureAttachmentType; use App\Form\Type\MasterPictureAttachmentType;
use App\Form\Type\SIUnitType; use App\Form\Type\SIUnitType;
@ -225,6 +224,7 @@ class PartBaseType extends AbstractType
'entry_type' => PartLotType::class, 'entry_type' => PartLotType::class,
'allow_add' => $this->security->isGranted('lots.create', $part), 'allow_add' => $this->security->isGranted('lots.create', $part),
'allow_delete' => $this->security->isGranted('lots.delete', $part), 'allow_delete' => $this->security->isGranted('lots.delete', $part),
'reindex_enable' => true,
'label' => false, 'label' => false,
'entry_options' => [ 'entry_options' => [
'measurement_unit' => $part->getPartUnit(), 'measurement_unit' => $part->getPartUnit(),
@ -238,6 +238,7 @@ class PartBaseType extends AbstractType
'entry_type' => AttachmentFormType::class, 'entry_type' => AttachmentFormType::class,
'allow_add' => $this->security->isGranted('attachments.create', $part), 'allow_add' => $this->security->isGranted('attachments.create', $part),
'allow_delete' => $this->security->isGranted('attachments.delete', $part), 'allow_delete' => $this->security->isGranted('attachments.delete', $part),
'reindex_enable' => true,
'label' => false, 'label' => false,
'entry_options' => [ 'entry_options' => [
'data_class' => PartAttachment::class, 'data_class' => PartAttachment::class,
@ -258,6 +259,7 @@ class PartBaseType extends AbstractType
'entry_type' => OrderdetailType::class, 'entry_type' => OrderdetailType::class,
'allow_add' => $this->security->isGranted('orderdetails.create', $part), 'allow_add' => $this->security->isGranted('orderdetails.create', $part),
'allow_delete' => $this->security->isGranted('orderdetails.delete', $part), 'allow_delete' => $this->security->isGranted('orderdetails.delete', $part),
'reindex_enable' => true,
'label' => false, 'label' => false,
'by_reference' => false, 'by_reference' => false,
'prototype_data' => new Orderdetail(), 'prototype_data' => new Orderdetail(),
@ -272,6 +274,7 @@ class PartBaseType extends AbstractType
'allow_add' => $this->security->isGranted('parameters.create', $part), 'allow_add' => $this->security->isGranted('parameters.create', $part),
'allow_delete' => $this->security->isGranted('parameters.delete', $part), 'allow_delete' => $this->security->isGranted('parameters.delete', $part),
'label' => false, 'label' => false,
'reindex_enable' => true,
'by_reference' => false, 'by_reference' => false,
'prototype_data' => new PartParameter(), 'prototype_data' => new PartParameter(),
'entry_options' => [ 'entry_options' => [

View file

@ -49,7 +49,7 @@ use App\Form\Type\SIUnitType;
use App\Form\Type\StructuralEntityType; use App\Form\Type\StructuralEntityType;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
@ -88,6 +88,7 @@ class PartLotType extends AbstractType
$builder->add('amount', SIUnitType::class, [ $builder->add('amount', SIUnitType::class, [
'measurement_unit' => $options['measurement_unit'], 'measurement_unit' => $options['measurement_unit'],
'required' => false,
'label' => 'part_lot.edit.amount', 'label' => 'part_lot.edit.amount',
'attr' => [ 'attr' => [
'class' => 'form-control-sm', 'class' => 'form-control-sm',
@ -116,9 +117,11 @@ class PartLotType extends AbstractType
'required' => false, 'required' => false,
]); ]);
$builder->add('expirationDate', DateTimeType::class, [ $builder->add('expirationDate', DateType::class, [
'label' => 'part_lot.edit.expiration_date', 'label' => 'part_lot.edit.expiration_date',
'attr' => [], 'attr' => [],
'widget' => 'single_text',
'model_timezone' => 'UTC',
'required' => false, 'required' => false,
]); ]);

View file

@ -86,7 +86,7 @@ class PermissionsType extends AbstractType
{ {
$groups = $this->perm_structure['groups']; $groups = $this->perm_structure['groups'];
foreach ($groups as $key => $group) { foreach (array_keys($groups) as $key) {
$builder->add($key, PermissionGroupType::class, [ $builder->add($key, PermissionGroupType::class, [
'group_name' => $key, 'group_name' => $key,
'mapped' => false, 'mapped' => false,

View file

@ -102,7 +102,7 @@ class CurrencyEntityType extends StructuralEntityType
$level -= $parent->getLevel() - 1; $level -= $parent->getLevel() - 1;
} }
$tmp = str_repeat('&nbsp;&nbsp;&nbsp;', $choice->getLevel()); //Use 3 spaces for intendation $tmp = str_repeat('&nbsp;&nbsp;&nbsp;', $level); //Use 3 spaces for intendation
if (empty($choice->getIsoCode())) { if (empty($choice->getIsoCode())) {
$tmp .= htmlspecialchars($choice->getName()); $tmp .= htmlspecialchars($choice->getName());
} else { } else {

View file

@ -153,37 +153,37 @@ class StructuralEntityType extends AbstractType
} }
/** /**
* Transforms a value from the original representation to a transformed representation. * Transforms a value from the original representation to a transformed representation.
* *
* This method is called when the form field is initialized with its default data, on * This method is called when the form field is initialized with its default data, on
* two occasions for two types of transformers: * two occasions for two types of transformers:
* *
* 1. Model transformers which normalize the model data. * 1. Model transformers which normalize the model data.
* This is mainly useful when the same form type (the same configuration) * This is mainly useful when the same form type (the same configuration)
* has to handle different kind of underlying data, e.g The DateType can * has to handle different kind of underlying data, e.g The DateType can
* deal with strings or \DateTime objects as input. * deal with strings or \DateTime objects as input.
* *
* 2. View transformers which adapt the normalized data to the view format. * 2. View transformers which adapt the normalized data to the view format.
* a/ When the form is simple, the value returned by convention is used * a/ When the form is simple, the value returned by convention is used
* directly in the view and thus can only be a string or an array. In * directly in the view and thus can only be a string or an array. In
* this case the data class should be null. * this case the data class should be null.
* *
* b/ When the form is compound the returned value should be an array or * b/ When the form is compound the returned value should be an array or
* an object to be mapped to the children. Each property of the compound * an object to be mapped to the children. Each property of the compound
* data will be used as model data by each child and will be transformed * data will be used as model data by each child and will be transformed
* too. In this case data class should be the class of the object, or null * too. In this case data class should be the class of the object, or null
* when it is an array. * when it is an array.
* *
* All transformers are called in a configured order from model data to view value. * All transformers are called in a configured order from model data to view value.
* At the end of this chain the view data will be validated against the data class * At the end of this chain the view data will be validated against the data class
* setting. * setting.
* *
* This method must be able to deal with empty values. Usually this will * This method must be able to deal with empty values. Usually this will
* be NULL, but depending on your implementation other empty values are * be NULL, but depending on your implementation other empty values are
* possible as well (such as empty strings). The reasoning behind this is * possible as well (such as empty strings). The reasoning behind this is
* that data transformers must be chainable. If the transform() method * that data transformers must be chainable. If the transform() method
* of the first data transformer outputs NULL, the second must be able to * of the first data transformer outputs NULL, the second must be able to
* process that value. * process that value.
* *
* @param mixed $value The value in the original representation * @param mixed $value The value in the original representation
* *
@ -191,31 +191,31 @@ class StructuralEntityType extends AbstractType
* *
* @throws TransformationFailedException when the transformation fails * @throws TransformationFailedException when the transformation fails
*/ */
public function transform($value, $options) public function transform($value, array $options)
{ {
return $value; return $value;
} }
/** /**
* Transforms a value from the transformed representation to its original * Transforms a value from the transformed representation to its original
* representation. * representation.
* *
* This method is called when {@link Form::submit()} is called to transform the requests tainted data * This method is called when {@link Form::submit()} is called to transform the requests tainted data
* into an acceptable format. * into an acceptable format.
* *
* The same transformers are called in the reverse order so the responsibility is to * The same transformers are called in the reverse order so the responsibility is to
* return one of the types that would be expected as input of transform(). * return one of the types that would be expected as input of transform().
* *
* This method must be able to deal with empty values. Usually this will * This method must be able to deal with empty values. Usually this will
* be an empty string, but depending on your implementation other empty * be an empty string, but depending on your implementation other empty
* values are possible as well (such as NULL). The reasoning behind * values are possible as well (such as NULL). The reasoning behind
* this is that value transformers must be chainable. If the * this is that value transformers must be chainable. If the
* reverseTransform() method of the first value transformer outputs an * reverseTransform() method of the first value transformer outputs an
* empty string, the second value transformer must be able to process that * empty string, the second value transformer must be able to process that
* value. * value.
* *
* By convention, reverseTransform() should return NULL if an empty string * By convention, reverseTransform() should return NULL if an empty string
* is passed. * is passed.
* *
* @param mixed $value The value in the transformed representation * @param mixed $value The value in the transformed representation
* *
@ -223,7 +223,7 @@ class StructuralEntityType extends AbstractType
* *
* @throws TransformationFailedException when the transformation fails * @throws TransformationFailedException when the transformation fails
*/ */
public function reverseTransform($value, $options) public function reverseTransform($value, array $options)
{ {
/* This step is important in combination with the caching! /* This step is important in combination with the caching!
The elements deserialized from cache, are not known to Doctrinte ORM any more, so doctrine thinks, The elements deserialized from cache, are not known to Doctrinte ORM any more, so doctrine thinks,
@ -272,7 +272,7 @@ class StructuralEntityType extends AbstractType
$level -= $parent->getLevel() - 1; $level -= $parent->getLevel() - 1;
} }
$tmp = str_repeat('&nbsp;&nbsp;&nbsp;', $choice->getLevel()); //Use 3 spaces for intendation $tmp = str_repeat('&nbsp;&nbsp;&nbsp;', $level); //Use 3 spaces for intendation
$tmp .= htmlspecialchars($choice->getName()); $tmp .= htmlspecialchars($choice->getName());
return $tmp; return $tmp;

View file

@ -244,6 +244,7 @@ class UserAdminForm extends AbstractType
'allow_add' => true, 'allow_add' => true,
'allow_delete' => true, 'allow_delete' => true,
'label' => false, 'label' => false,
'reindex_enable' => true,
'entry_options' => [ 'entry_options' => [
'data_class' => $options['attachment_class'], 'data_class' => $options['attachment_class'],
], ],

View file

@ -46,11 +46,6 @@ use JsonSerializable;
final class TreeViewNodeState implements JsonSerializable final class TreeViewNodeState implements JsonSerializable
{ {
/**
* @var bool|null
*/
private $checked = null;
/** /**
* @var bool|null * @var bool|null
*/ */

View file

@ -41,7 +41,7 @@ final class Version1 extends AbstractMigration
try { try {
//Check if we can use this migration method: //Check if we can use this migration method:
$version = (int) $this->connection->fetchColumn("SELECT keyValue AS version FROM `internal` WHERE `keyName` = 'dbVersion'"); $version = (int) $this->connection->fetchColumn("SELECT keyValue AS version FROM `internal` WHERE `keyName` = 'dbVersion'");
$this->skipIf(true, 'Old Part-DB Database detected! Continue with upgrade...'); $this->skipIf($version > 0, 'Old Part-DB Database detected! Continue with upgrade...');
} catch (DBALException $dBALException) { } catch (DBALException $dBALException) {
//when the table was not found, we can proceed, because we have an empty DB! //when the table was not found, we can proceed, because we have an empty DB!
} }

View file

@ -27,11 +27,11 @@ final class Version20200311204104 extends AbstractMigration
$this->addSql('ALTER TABLE `users` ADD perms_parts_parameters SMALLINT NOT NULL'); $this->addSql('ALTER TABLE `users` ADD perms_parts_parameters SMALLINT NOT NULL');
$this->addSql('ALTER TABLE log CHANGE level level TINYINT'); $this->addSql('ALTER TABLE log CHANGE level level TINYINT');
$sql = 'UPDATE `groups`' . $sql = 'UPDATE `groups`'.
'SET perms_parts_parameters = 341 WHERE (id = 1 AND name = "admins") OR (id = 3 AND name = "users");'; 'SET perms_parts_parameters = 341 WHERE (id = 1 AND name = "admins") OR (id = 3 AND name = "users");';
$this->addSql($sql); $this->addSql($sql);
$sql = 'UPDATE `groups`' . $sql = 'UPDATE `groups`'.
'SET perms_parts_parameters = 681 WHERE (id = 2 AND name = "readonly");'; 'SET perms_parts_parameters = 681 WHERE (id = 2 AND name = "readonly");';
$this->addSql($sql); $this->addSql($sql);

View file

@ -44,12 +44,13 @@ class DBElementRepository extends EntityRepository
->setParameter(1, $element->getID()) ->setParameter(1, $element->getID())
->getQuery(); ->getQuery();
$p = $q->execute(); //Do the renaming
$q->execute();
$this->setField($element, 'id', $new_id); $this->setField($element, 'id', $new_id);
} }
protected function setField(AbstractDBElement $element, string $field, $new_value): void protected function setField(AbstractDBElement $element, string $field, int $new_value): void
{ {
$reflection = new \ReflectionClass(get_class($element)); $reflection = new \ReflectionClass(get_class($element));
$property = $reflection->getProperty($field); $property = $reflection->getProperty($field);

View file

@ -142,7 +142,6 @@ class ElementPermissionListener
return; return;
} }
$em = $eventArgs->getEntityManager();
$unitOfWork = $eventArgs->getEntityManager()->getUnitOfWork(); $unitOfWork = $eventArgs->getEntityManager()->getUnitOfWork();
$reflectionClass = new ReflectionClass($element); $reflectionClass = new ReflectionClass($element);

View file

@ -43,6 +43,7 @@ declare(strict_types=1);
namespace App\Security\Voter; namespace App\Security\Voter;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Repository\UserRepository;
use App\Services\PermissionResolver; use App\Services\PermissionResolver;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
@ -76,6 +77,7 @@ abstract class ExtendedVoter extends Voter
// if the user is anonymous, we use the anonymous user. // if the user is anonymous, we use the anonymous user.
if (! $user instanceof User) { if (! $user instanceof User) {
/** @var UserRepository $repo */
$repo = $this->entityManager->getRepository(User::class); $repo = $this->entityManager->getRepository(User::class);
$user = $repo->getAnonymousUser(); $user = $repo->getAnonymousUser();
if (null === $user) { if (null === $user) {

View file

@ -60,10 +60,11 @@ class AmountFormatter
} }
/** /**
* Formats the given value using the measurement unit and options. * Formats the given value using the measurement unit and options.
* *
* @param MeasurementUnit|null $unit The measurement unit, whose unit symbol should be used for formatting. * @param float|string|int $value
* If set to null, it is assumed that the part amount is measured in pieces. * @param MeasurementUnit|null $unit The measurement unit, whose unit symbol should be used for formatting.
* If set to null, it is assumed that the part amount is measured in pieces.
* *
* @return string The formatted string * @return string The formatted string
* *

View file

@ -130,7 +130,7 @@ 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()) { if ($attachment->isExternal() && !empty($attachment->getURL())) {
return $attachment->getURL(); return $attachment->getURL();
} }

View file

@ -43,10 +43,10 @@ declare(strict_types=1);
namespace App\Services\Attachments; namespace App\Services\Attachments;
use App\Entity\Attachments\Attachment; use App\Entity\Attachments\Attachment;
use Psr\Cache\InvalidArgumentException;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Contracts\Cache\CacheInterface; use Symfony\Contracts\Cache\CacheInterface;
use Psr\Cache\InvalidArgumentException;
/** /**
* This service is used to find builtin attachment ressources. * This service is used to find builtin attachment ressources.

View file

@ -174,7 +174,7 @@ class FileTypeFilterTools
$extensions = array_merge($extensions, static::IMAGE_EXTS); $extensions = array_merge($extensions, static::IMAGE_EXTS);
} elseif ('audio/*' === $element) { } elseif ('audio/*' === $element) {
$extensions = array_merge($extensions, static::AUDIO_EXTS); $extensions = array_merge($extensions, static::AUDIO_EXTS);
} elseif ('image/*' === $element) { } elseif ('video/*' === $element) {
$extensions = array_merge($extensions, static::VIDEO_EXTS); $extensions = array_merge($extensions, static::VIDEO_EXTS);
} elseif (preg_match('#^[-\w.]+\/[-\w.*]+#', $element)) { } elseif (preg_match('#^[-\w.]+\/[-\w.*]+#', $element)) {
$extensions = array_merge($extensions, $this->mimeTypes->getExtensions($element)); $extensions = array_merge($extensions, $this->mimeTypes->getExtensions($element));

View file

@ -55,13 +55,15 @@ class PartPreviewGenerator
} }
/** /**
* Returns a list of attachments that can be used for previewing the part ordered by priority. * Returns a list of attachments that can be used for previewing the part ordered by priority.
* The priority is: Part MasterAttachment -> Footprint MasterAttachment -> Category MasterAttachment * The priority is: Part MasterAttachment -> Footprint MasterAttachment -> Category MasterAttachment
* -> Storelocation Attachment -> MeasurementUnit Attachment -> ManufacturerAttachment. * -> Storelocation Attachment -> MeasurementUnit Attachment -> ManufacturerAttachment.
* *
* @param Part $part the part for which the attachments should be determined * @param Part $part the part for which the attachments should be determined
* *
* @return Attachment[] * @return (Attachment|null)[]
*
* @psalm-return list<Attachment|null>
*/ */
public function getPreviewAttachments(Part $part): array public function getPreviewAttachments(Part $part): array
{ {

View file

@ -102,7 +102,7 @@ class ElementTypeNameGenerator
* Useful when the type should be shown to user. * Useful when the type should be shown to user.
* Throws an exception if the class is not supported. * Throws an exception if the class is not supported.
* *
* @param AbstractDBElement|string $entity The element or class for which the label should be generated * @param object|string $entity The element or class for which the label should be generated
* *
* @return string the localized label for the entity type * @return string the localized label for the entity type
* *

View file

@ -77,9 +77,10 @@ class EntityExporter
} }
/** /**
* Exports an Entity or an array of entities to multiple file formats. * Exports an Entity or an array of entities to multiple file formats.
* *
* @param Request $request the request that should be used for option resolving * @param Request $request the request that should be used for option resolving
* @param AbstractNamedDBElement|object[] $entity
* *
* @return Response the generated response containing the exported data * @return Response the generated response containing the exported data
* *

View file

@ -94,12 +94,12 @@ class EntityURLGenerator
* @param mixed $entity The element for which the page should be generated * @param mixed $entity The element for which the page should be generated
* @param string $type The page type. Currently supported: 'info', 'edit', 'create', 'clone', 'list'/'list_parts' * @param string $type The page type. Currently supported: 'info', 'edit', 'create', 'clone', 'list'/'list_parts'
* *
* @return string the link to the desired page * @return null|string the link to the desired page
* *
* @throws EntityNotSupportedException thrown if the entity is not supported for the given type * @throws EntityNotSupportedException thrown if the entity is not supported for the given type
* @throws InvalidArgumentException thrown if the givent type is not existing * @throws InvalidArgumentException thrown if the givent type is not existing
*/ */
public function getURL($entity, string $type) public function getURL($entity, string $type): ?string
{ {
switch ($type) { switch ($type) {
case 'info': case 'info':
@ -187,7 +187,7 @@ class EntityURLGenerator
throw new EntityNotSupportedException('The given entity is not supported yet!'); throw new EntityNotSupportedException('The given entity is not supported yet!');
} }
public function viewURL($entity): string public function viewURL(Attachment $entity): ?string
{ {
if ($entity instanceof Attachment) { if ($entity instanceof Attachment) {
if ($entity->isExternal()) { //For external attachments, return the link to external path if ($entity->isExternal()) { //For external attachments, return the link to external path
@ -201,7 +201,7 @@ class EntityURLGenerator
throw new EntityNotSupportedException('The given entity is not supported yet!'); throw new EntityNotSupportedException('The given entity is not supported yet!');
} }
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 if ($entity->isExternal()) { //For external attachments, return the link to external path
@ -383,7 +383,7 @@ class EntityURLGenerator
//Check if we have an direct mapping for the given class //Check if we have an direct mapping for the given class
if (! array_key_exists($class, $map)) { if (! array_key_exists($class, $map)) {
//Check if we need to check inheritance by looping through our map //Check if we need to check inheritance by looping through our map
foreach ($map as $key => $value) { foreach (array_keys($map) as $key) {
if (is_a($entity, $key)) { if (is_a($entity, $key)) {
return $map[$key]; return $map[$key];
} }

View file

@ -60,10 +60,14 @@ class GitVersionInfo
*/ */
public function getGitBranchName() public function getGitBranchName()
{ {
if (file_exists($this->project_dir.'/.git/HEAD')) { if (is_file($this->project_dir.'/.git/HEAD')) {
$git = file($this->project_dir.'/.git/HEAD'); $git = file($this->project_dir.'/.git/HEAD');
$head = explode('/', $git[0], 3); $head = explode('/', $git[0], 3);
if (! isset($head[2])) {
return null;
}
return trim($head[2]); return trim($head[2]);
} }
@ -82,8 +86,13 @@ class GitVersionInfo
public function getGitCommitHash(int $length = 7) public function getGitCommitHash(int $length = 7)
{ {
$filename = $this->project_dir.'/.git/refs/remotes/origin/'.$this->getGitBranchName(); $filename = $this->project_dir.'/.git/refs/remotes/origin/'.$this->getGitBranchName();
if (file_exists($filename)) { if (is_file($filename)) {
$head = file($filename); $head = file($filename);
if (! isset($head[0])) {
return null;
}
$hash = $head[0]; $hash = $head[0];
return substr($hash, 0, $length); return substr($hash, 0, $length);

View file

@ -31,7 +31,7 @@ class EventCommentHelper
public function __construct() public function __construct()
{ {
$message = null; $this->message = null;
} }
/** /**

View file

@ -37,7 +37,7 @@ class EventUndoHelper
public function __construct() public function __construct()
{ {
$undone_event = null; $this->undone_event = null;
$this->mode = self::MODE_UNDO; $this->mode = self::MODE_UNDO;
} }

View file

@ -35,10 +35,12 @@ class HistoryHelper
} }
/** /**
* Returns an array containing all elements that are associated with the argument. * Returns an array containing all elements that are associated with the argument.
* The returned array contains the given element. * The returned array contains the given element.
* *
* @return array * @return array
*
* @psalm-return array<\App\Entity\Parameters\AbstractParameter|array-key, mixed>
*/ */
public function getAssociatedElements(AbstractDBElement $element): array public function getAssociatedElements(AbstractDBElement $element): array
{ {

View file

@ -209,6 +209,9 @@ class TimeTravel
return $property->getValue($element); return $property->getValue($element);
} }
/**
* @param \DateTime|int|null $new_value
*/
protected function setField(AbstractDBElement $element, string $field, $new_value): void protected function setField(AbstractDBElement $element, string $field, $new_value): void
{ {
$reflection = new \ReflectionClass(get_class($element)); $reflection = new \ReflectionClass(get_class($element));

View file

@ -1,4 +1,7 @@
<?php <?php
declare(strict_types=1);
/** /**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony). * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
* *
@ -20,31 +23,29 @@
namespace App\Services\Parameters; namespace App\Services\Parameters;
use App\Entity\Parameters\AbstractParameter; use App\Entity\Parameters\AbstractParameter;
use App\Entity\Parameters\PartParameter; use App\Entity\Parameters\PartParameter;
class ParameterExtractor class ParameterExtractor
{ {
protected const ALLOWED_PARAM_SEPARATORS = [", ", "\n"]; protected const ALLOWED_PARAM_SEPARATORS = [', ', "\n"];
protected const CHAR_LIMIT = 1000; protected const CHAR_LIMIT = 1000;
/** /**
* Tries to extract parameters from the given string. * Tries to extract parameters from the given string.
* Useful for extraction from part description and comment. * Useful for extraction from part description and comment.
* @param string $input *
* @param string $class
* @return AbstractParameter[] * @return AbstractParameter[]
*/ */
public function extractParameters(string $input, string $class = PartParameter::class): array public function extractParameters(string $input, string $class = PartParameter::class): array
{ {
if (!is_a($class, AbstractParameter::class, true)) { if (! is_a($class, AbstractParameter::class, true)) {
throw new \InvalidArgumentException('$class must be a child class of AbstractParameter!'); throw new \InvalidArgumentException('$class must be a child class of AbstractParameter!');
} }
//Restrict search length //Restrict search length
$input = mb_strimwidth($input,0,self::CHAR_LIMIT); $input = mb_strimwidth($input, 0, self::CHAR_LIMIT);
$parameters = []; $parameters = [];
@ -52,7 +53,7 @@ class ParameterExtractor
$split = $this->splitString($input); $split = $this->splitString($input);
foreach ($split as $param_string) { foreach ($split as $param_string) {
$tmp = $this->stringToParam($param_string, $class); $tmp = $this->stringToParam($param_string, $class);
if ($tmp !== null) { if (null !== $tmp) {
$parameters[] = $tmp; $parameters[] = $tmp;
} }
} }
@ -67,9 +68,8 @@ class ParameterExtractor
$matches = []; $matches = [];
\preg_match($regex, $input, $matches); \preg_match($regex, $input, $matches);
dump($matches); if (! empty($matches)) {
if (!empty($matches)) { [, $name, $value] = $matches;
[$raw, $name, $value] = $matches;
$value = trim($value); $value = trim($value);
//Dont allow empty names or values (these are a sign of an invalid extracted string) //Dont allow empty names or values (these are a sign of an invalid extracted string)
@ -91,7 +91,8 @@ class ParameterExtractor
protected function splitString(string $input): array protected function splitString(string $input): array
{ {
//Allow comma as limiter (include space, to prevent splitting in german style numbers) //Allow comma as limiter (include space, to prevent splitting in german style numbers)
$input = str_replace(static::ALLOWED_PARAM_SEPARATORS, ";", $input); $input = str_replace(static::ALLOWED_PARAM_SEPARATORS, ';', $input);
return explode(";", $input);
return explode(';', $input);
} }
} }

View file

@ -62,12 +62,12 @@ class PermissionResolver
/** /**
* PermissionResolver constructor. * PermissionResolver constructor.
*/ */
public function __construct(ContainerInterface $container) public function __construct(bool $kernel_debug, string $kernel_cache_dir)
{ {
$cache_dir = $container->getParameter('kernel.cache_dir'); $cache_dir = $kernel_cache_dir;
//Here the cached structure will be saved. //Here the cached structure will be saved.
$this->cache_file = $cache_dir.'/permissions.php.cache'; $this->cache_file = $cache_dir.'/permissions.php.cache';
$this->is_debug = $container->getParameter('kernel.debug'); $this->is_debug = $kernel_debug;
$this->permission_structure = $this->generatePermissionStructure(); $this->permission_structure = $this->generatePermissionStructure();
@ -127,7 +127,7 @@ class PermissionResolver
return $allowed; return $allowed;
} }
/** @var HasPermissionsInterface $parent */ /** @var Group $parent */
$parent = $user->getGroup(); $parent = $user->getGroup();
while (null !== $parent) { //The top group, has parent == null while (null !== $parent) { //The top group, has parent == null
//Check if our current element gives a info about disallow/allow //Check if our current element gives a info about disallow/allow

View file

@ -30,12 +30,10 @@ use Twig\TwigFunction;
class LastUserExtension extends AbstractExtension class LastUserExtension extends AbstractExtension
{ {
private $em;
private $repo; private $repo;
public function __construct(EntityManagerInterface $em) public function __construct(EntityManagerInterface $em)
{ {
$this->em = $em;
$this->repo = $em->getRepository(AbstractLogEntry::class); $this->repo = $em->getRepository(AbstractLogEntry::class);
} }

View file

@ -42,7 +42,9 @@
</span> </span>
</h6> </h6>
{% endif %} {% endif %}
{% if attach.picture %} {% if attach.secure and not is_granted('show_private', attach) %}
{# Leave blank #}
{% elseif attach.picture %}
<a href="{{ attach | entityURL('file_view') }}" target="_blank" rel="noopener" data-no-ajax> <a href="{{ attach | entityURL('file_view') }}" target="_blank" rel="noopener" data-no-ajax>
<img class="img-fluid img-thumbnail thumbnail-sm" src="{{ attachment_thumbnail(attach, 'thumbnail_md') }}" alt="{% trans %}attachment.preview.alt{% endtrans %}" /> <img class="img-fluid img-thumbnail thumbnail-sm" src="{{ attachment_thumbnail(attach, 'thumbnail_md') }}" alt="{% trans %}attachment.preview.alt{% endtrans %}" />
</a> </a>
@ -60,7 +62,7 @@
{% if attach.secure %} {% if attach.secure %}
<h6> <h6>
<span class="badge badge-primary"> <span class="badge badge-success">
<i class="fas fa-fw fa-shield-alt"></i> {% trans %}attachment.secure{% endtrans %} <i class="fas fa-fw fa-shield-alt"></i> {% trans %}attachment.secure{% endtrans %}
</span> </span>
</h6> </h6>
@ -90,7 +92,7 @@
//Determine the table, so we can determine, how many entries there are already. //Determine the table, so we can determine, how many entries there are already.
$holder = $("#attachments_table"); $holder = $("#attachments_table");
var index = $holder.find(":input").length; var index = $holder.children("tbody").children("tr").length;
var newForm = $holder.data("prototype"); var newForm = $holder.data("prototype");
//Increase the index //Increase the index

View file

@ -39,7 +39,7 @@
//Determine the table, so we can determine, how many entries there are already. //Determine the table, so we can determine, how many entries there are already.
$holder = $(btn).siblings("table"); $holder = $(btn).siblings("table");
var index = $holder.find(":input").length; var index = $holder.children("tbody").children("tr").length;
var newForm = $holder.data("prototype"); var newForm = $holder.data("prototype");
//Increase the index //Increase the index

View file

@ -40,7 +40,9 @@
</h6> </h6>
{% endif %} {% endif %}
{% if attach.picture %} {% if attach.secure and not is_granted('show_private', attach) %}
{# Leave blank #}
{% elseif attach.picture %}
<a href="{{ attach | entityURL('file_view') }}" rel="noopener" target="_blank" data-no-ajax> <a href="{{ attach | entityURL('file_view') }}" rel="noopener" target="_blank" data-no-ajax>
<img class="img-fluid img-thumbnail thumbnail-sm" src="{{ attachment_thumbnail(attach, 'thumbnail_md') }}" alt="{% trans %}attachment.preview.alt{% endtrans %}" /> <img class="img-fluid img-thumbnail thumbnail-sm" src="{{ attachment_thumbnail(attach, 'thumbnail_md') }}" alt="{% trans %}attachment.preview.alt{% endtrans %}" />
</a> </a>
@ -58,7 +60,7 @@
{% if attach.secure %} {% if attach.secure %}
<h6> <h6>
<span class="badge badge-primary"> <span class="badge badge-success">
<i class="fas fa-fw fa-shield-alt"></i> {% trans %}attachment.secure{% endtrans %} <i class="fas fa-fw fa-shield-alt"></i> {% trans %}attachment.secure{% endtrans %}
</span> </span>
</h6> </h6>
@ -88,7 +90,7 @@
//Determine the table, so we can determine, how many entries there are already. //Determine the table, so we can determine, how many entries there are already.
$holder = $("#attachments_table"); $holder = $("#attachments_table");
var index = $holder.find(":input").length; var index = $holder.children("tbody").children("tr").length;
var newForm = $holder.data("prototype"); var newForm = $holder.data("prototype");
//Increase the index //Increase the index

Some files were not shown because too many files have changed in this diff Show more