mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-12-11 05:29:30 +00:00
Merge branch 'master' into settings-bundle
This commit is contained in:
commit
8750573724
191 changed files with 27745 additions and 12133 deletions
|
|
@ -35,6 +35,7 @@ use App\Entity\LabelSystem\LabelProcessMode;
|
|||
use App\Entity\LabelSystem\LabelProfile;
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use App\Exceptions\AttachmentDownloadException;
|
||||
use App\Exceptions\TwigModeException;
|
||||
use App\Form\AdminPages\ImportType;
|
||||
use App\Form\AdminPages\MassCreationForm;
|
||||
use App\Repository\AbstractPartsContainingRepository;
|
||||
|
|
@ -53,6 +54,7 @@ use InvalidArgumentException;
|
|||
use Omines\DataTablesBundle\DataTableFactory;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
|
@ -211,10 +213,14 @@ abstract class BaseAdminController extends AbstractController
|
|||
//Show preview for LabelProfile if needed.
|
||||
if ($entity instanceof LabelProfile) {
|
||||
$example = $this->barcodeExampleGenerator->getElement($entity->getOptions()->getSupportedElement());
|
||||
$pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example);
|
||||
$pdf_data = null;
|
||||
try {
|
||||
$pdf_data = $this->labelGenerator->generateLabel($entity->getOptions(), $example);
|
||||
} catch (TwigModeException $exception) {
|
||||
$form->get('options')->get('lines')->addError(new FormError($exception->getSafeMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
/** @var AbstractPartsContainingRepository $repo */
|
||||
$repo = $this->entityManager->getRepository($this->entity_class);
|
||||
|
||||
return $this->render($this->twig_template, [
|
||||
|
|
@ -390,7 +396,7 @@ abstract class BaseAdminController extends AbstractController
|
|||
{
|
||||
if ($entity instanceof AbstractPartsContainingDBElement) {
|
||||
/** @var AbstractPartsContainingRepository $repo */
|
||||
$repo = $this->entityManager->getRepository($this->entity_class);
|
||||
$repo = $this->entityManager->getRepository($this->entity_class); //@phpstan-ignore-line
|
||||
if ($repo->getPartsCount($entity) > 0) {
|
||||
$this->addFlash('error', t('entity.delete.must_not_contain_parts', ['%PATH%' => $entity->getFullPath()]));
|
||||
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@ class AttachmentFileController extends AbstractController
|
|||
}
|
||||
|
||||
if ($attachment->isExternal()) {
|
||||
throw new RuntimeException('You can not download external attachments!');
|
||||
throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!');
|
||||
}
|
||||
|
||||
if (!$helper->isFileExisting($attachment)) {
|
||||
throw new RuntimeException('The file associated with the attachment is not existing!');
|
||||
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
|
||||
}
|
||||
|
||||
$file_path = $helper->toAbsoluteFilePath($attachment);
|
||||
|
|
@ -82,11 +82,11 @@ class AttachmentFileController extends AbstractController
|
|||
}
|
||||
|
||||
if ($attachment->isExternal()) {
|
||||
throw new RuntimeException('You can not download external attachments!');
|
||||
throw $this->createNotFoundException('The file for this attachment is external and can not stored locally!');
|
||||
}
|
||||
|
||||
if (!$helper->isFileExisting($attachment)) {
|
||||
throw new RuntimeException('The file associated with the attachment is not existing!');
|
||||
throw $this->createNotFoundException('The file associated with the attachment is not existing!');
|
||||
}
|
||||
|
||||
$file_path = $helper->toAbsoluteFilePath($attachment);
|
||||
|
|
|
|||
|
|
@ -23,10 +23,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Parts\Manufacturer;
|
||||
use App\Entity\Parts\Part;
|
||||
use App\Form\InfoProviderSystem\PartSearchType;
|
||||
use App\Services\InfoProviderSystem\ExistingPartFinder;
|
||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
|
|
@ -42,7 +45,9 @@ class InfoProviderController extends AbstractController
|
|||
{
|
||||
|
||||
public function __construct(private readonly ProviderRegistry $providerRegistry,
|
||||
private readonly PartInfoRetriever $infoRetriever)
|
||||
private readonly PartInfoRetriever $infoRetriever,
|
||||
private readonly ExistingPartFinder $existingPartFinder
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -72,21 +77,49 @@ class InfoProviderController extends AbstractController
|
|||
//When we are updating a part, use its name as keyword, to make searching easier
|
||||
//However we can only do this, if the form was not submitted yet
|
||||
if ($update_target !== null && !$form->isSubmitted()) {
|
||||
$form->get('keyword')->setData($update_target->getName());
|
||||
//Use the provider reference if available, otherwise use the manufacturer product number
|
||||
$keyword = $update_target->getProviderReference()->getProviderId() ?? $update_target->getManufacturerProductNumber();
|
||||
//Or the name if both are not available
|
||||
if ($keyword === "") {
|
||||
$keyword = $update_target->getName();
|
||||
}
|
||||
|
||||
$form->get('keyword')->setData($keyword);
|
||||
|
||||
//If we are updating a part, which already has a provider, preselect that provider in the form
|
||||
if ($update_target->getProviderReference()->getProviderKey() !== null) {
|
||||
try {
|
||||
$form->get('providers')->setData([$this->providerRegistry->getProviderByKey($update_target->getProviderReference()->getProviderKey())]);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
//If the provider is not found, just ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$keyword = $form->get('keyword')->getData();
|
||||
$providers = $form->get('providers')->getData();
|
||||
|
||||
$dtos = [];
|
||||
|
||||
try {
|
||||
$results = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
|
||||
$dtos = $this->infoRetriever->searchByKeyword(keyword: $keyword, providers: $providers);
|
||||
} catch (ClientException $e) {
|
||||
$this->addFlash('error', t('info_providers.search.error.client_exception'));
|
||||
$this->addFlash('error',$e->getMessage());
|
||||
//Log the exception
|
||||
$exceptionLogger->error('Error during info provider search: ' . $e->getMessage(), ['exception' => $e]);
|
||||
}
|
||||
|
||||
// modify the array to an array of arrays that has a field for a matching local Part
|
||||
// the advantage to use that format even when we don't look for local parts is that we
|
||||
// always work with the same interface
|
||||
$results = array_map(function ($result) {return ['dto' => $result, 'localPart' => null];}, $dtos);
|
||||
if(!$update_target) {
|
||||
foreach ($results as $index => $result) {
|
||||
$results[$index]['localPart'] = $this->existingPartFinder->findFirstExisting($result['dto']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('info_providers/search/part_search.html.twig', [
|
||||
|
|
|
|||
|
|
@ -108,8 +108,31 @@ class LabelController extends AbstractController
|
|||
$pdf_data = null;
|
||||
$filename = 'invalid.pdf';
|
||||
|
||||
//Generate PDF either when the form is submitted and valid, or the form was not submit yet, and generate is set
|
||||
if (($form->isSubmitted() && $form->isValid()) || ($generate && !$form->isSubmitted() && $profile instanceof LabelProfile)) {
|
||||
|
||||
//Check if the label should be saved as profile
|
||||
if ($form->get('save_profile')->isClicked() && $this->isGranted('@labels.create_profiles')) { //@phpstan-ignore-line Phpstan does not recognize the isClicked method
|
||||
//Retrieve the profile name from the form
|
||||
$new_name = $form->get('save_profile_name')->getData();
|
||||
//ensure that the name is not empty
|
||||
if ($new_name === '' || $new_name === null) {
|
||||
$form->get('save_profile_name')->addError(new FormError($this->translator->trans('label_generator.profile_name_empty')));
|
||||
goto render;
|
||||
}
|
||||
|
||||
$profile = new LabelProfile();
|
||||
$profile->setName($form->get('save_profile_name')->getData());
|
||||
$profile->setOptions($form_options);
|
||||
$this->em->persist($profile);
|
||||
$this->em->flush();
|
||||
$this->addFlash('success', 'label_generator.profile_saved');
|
||||
|
||||
return $this->redirectToRoute('label_dialog_profile', [
|
||||
'profile' => $profile->getID(),
|
||||
'target_id' => (string) $form->get('target_id')->getData()
|
||||
]);
|
||||
}
|
||||
|
||||
$target_id = (string) $form->get('target_id')->getData();
|
||||
$targets = $this->findObjects($form_options->getSupportedElement(), $target_id);
|
||||
if ($targets !== []) {
|
||||
|
|
@ -117,7 +140,7 @@ class LabelController extends AbstractController
|
|||
$pdf_data = $this->labelGenerator->generateLabel($form_options, $targets);
|
||||
$filename = $this->getLabelName($targets[0], $profile);
|
||||
} catch (TwigModeException $exception) {
|
||||
$form->get('options')->get('lines')->addError(new FormError($exception->getMessage()));
|
||||
$form->get('options')->get('lines')->addError(new FormError($exception->getSafeMessage()));
|
||||
}
|
||||
} else {
|
||||
//$this->addFlash('warning', 'label_generator.no_entities_found');
|
||||
|
|
@ -132,6 +155,7 @@ class LabelController extends AbstractController
|
|||
}
|
||||
}
|
||||
|
||||
render:
|
||||
return $this->render('label_system/dialog.html.twig', [
|
||||
'form' => $form,
|
||||
'pdf_data' => $pdf_data,
|
||||
|
|
@ -152,7 +176,7 @@ class LabelController extends AbstractController
|
|||
{
|
||||
$id_array = $this->rangeParser->parse($ids);
|
||||
|
||||
/** @var DBElementRepository $repo */
|
||||
/** @var DBElementRepository<AbstractDBElement> $repo */
|
||||
$repo = $this->em->getRepository($type->getEntityClass());
|
||||
|
||||
return $repo->getElementsFromIDArray($id_array);
|
||||
|
|
|
|||
|
|
@ -229,6 +229,10 @@ class PartController extends AbstractController
|
|||
$dto = $infoRetriever->getDetails($providerKey, $providerId);
|
||||
$new_part = $infoRetriever->dtoToPart($dto);
|
||||
|
||||
if ($new_part->getCategory() === null || $new_part->getCategory()->getID() === null) {
|
||||
$this->addFlash('warning', t("part.create_from_info_provider.no_category_yet"));
|
||||
}
|
||||
|
||||
return $this->renderPartForm('new', $request, $new_part, [
|
||||
'info_provider_dto' => $dto,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ class PartListsController extends AbstractController
|
|||
$ids = $request->request->get('ids');
|
||||
$action = $request->request->get('action');
|
||||
$target = $request->request->get('target');
|
||||
$redirectResponse = null;
|
||||
|
||||
if (!$this->isCsrfTokenValid('table_action', $request->request->get('_token'))) {
|
||||
$this->addFlash('error', 'csfr_invalid');
|
||||
|
|
@ -86,7 +87,7 @@ class PartListsController extends AbstractController
|
|||
}
|
||||
|
||||
//If the action handler returned a response, we use it, otherwise we redirect back to the previous page.
|
||||
if (isset($redirectResponse) && $redirectResponse instanceof Response) {
|
||||
if ($redirectResponse !== null) {
|
||||
return $redirectResponse;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ declare(strict_types=1);
|
|||
namespace App\Controller;
|
||||
|
||||
use App\Form\LabelSystem\ScanDialogType;
|
||||
use App\Services\LabelSystem\Barcodes\BarcodeScanHelper;
|
||||
use App\Services\LabelSystem\Barcodes\BarcodeRedirector;
|
||||
use App\Services\LabelSystem\Barcodes\BarcodeScanResult;
|
||||
use App\Services\LabelSystem\Barcodes\BarcodeSourceType;
|
||||
use App\Services\LabelSystem\BarcodeScanner\BarcodeRedirector;
|
||||
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper;
|
||||
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType;
|
||||
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult;
|
||||
use Doctrine\ORM\EntityNotFoundException;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
|
|
@ -77,13 +77,21 @@ class ScanController extends AbstractController
|
|||
$mode = $form['mode']->getData();
|
||||
}
|
||||
|
||||
$infoModeData = null;
|
||||
|
||||
if ($input !== null) {
|
||||
try {
|
||||
$scan_result = $this->barcodeNormalizer->scanBarcodeContent($input, $mode ?? null);
|
||||
try {
|
||||
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
|
||||
} catch (EntityNotFoundException) {
|
||||
$this->addFlash('success', 'scan.qr_not_found');
|
||||
//Perform a redirect if the info mode is not enabled
|
||||
if (!$form['info_mode']->getData()) {
|
||||
try {
|
||||
return $this->redirect($this->barcodeParser->getRedirectURL($scan_result));
|
||||
} catch (EntityNotFoundException) {
|
||||
$this->addFlash('success', 'scan.qr_not_found');
|
||||
}
|
||||
} else { //Otherwise retrieve infoModeData
|
||||
$infoModeData = $scan_result->getDecodedForInfoMode();
|
||||
|
||||
}
|
||||
} catch (InvalidArgumentException) {
|
||||
$this->addFlash('error', 'scan.format_unknown');
|
||||
|
|
@ -92,6 +100,7 @@ class ScanController extends AbstractController
|
|||
|
||||
return $this->render('label_system/scanner/scanner.html.twig', [
|
||||
'form' => $form,
|
||||
'infoModeData' => $infoModeData,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +118,7 @@ class ScanController extends AbstractController
|
|||
throw new InvalidArgumentException('Unknown type: '.$type);
|
||||
}
|
||||
//Construct the scan result manually, as we don't have a barcode here
|
||||
$scan_result = new BarcodeScanResult(
|
||||
$scan_result = new LocalBarcodeScanResult(
|
||||
target_type: BarcodeScanHelper::QR_TYPE_MAP[$type],
|
||||
target_id: $id,
|
||||
//The routes are only used on the internal generated QR codes
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@ class ToolsController extends AbstractController
|
|||
'default_theme' => $settings->system->customization->theme,
|
||||
'enabled_locales' => $this->getParameter('partdb.locale_menu'),
|
||||
'demo_mode' => $this->getParameter('partdb.demo_mode'),
|
||||
'gpdr_compliance' => $this->getParameter('partdb.gdpr_compliance'),
|
||||
'use_gravatar' => $settings->system->privacy->useGravatar,
|
||||
'gdpr_compliance' => $this->getParameter('partdb.gdpr_compliance'),
|
||||
'email_password_reset' => $this->getParameter('partdb.users.email_pw_reset'),
|
||||
'enviroment' => $this->getParameter('kernel.environment'),
|
||||
'environment' => $this->getParameter('kernel.environment'),
|
||||
'is_debug' => $this->getParameter('kernel.debug'),
|
||||
'email_sender' => $this->getParameter('partdb.mail.sender_email'),
|
||||
'email_sender_name' => $this->getParameter('partdb.mail.sender_name'),
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Parameters\AbstractParameter;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use App\Entity\Attachments\Attachment;
|
||||
use App\Entity\Parts\Category;
|
||||
|
|
@ -92,7 +93,7 @@ class TypeaheadController extends AbstractController
|
|||
|
||||
/**
|
||||
* This function map the parameter type to the class, so we can access its repository
|
||||
* @return class-string
|
||||
* @return class-string<AbstractParameter>
|
||||
*/
|
||||
private function typeToParameterClass(string $type): string
|
||||
{
|
||||
|
|
@ -155,7 +156,7 @@ class TypeaheadController extends AbstractController
|
|||
//Ensure user has the correct permissions
|
||||
$this->denyAccessUnlessGranted('read', $test_obj);
|
||||
|
||||
/** @var ParameterRepository $repository */
|
||||
/** @var ParameterRepository<AbstractParameter> $repository */
|
||||
$repository = $entityManager->getRepository($class);
|
||||
|
||||
$data = $repository->autocompleteParamName($query);
|
||||
|
|
|
|||
|
|
@ -240,7 +240,10 @@ class UserSettingsController extends AbstractController
|
|||
$page_need_reload = true;
|
||||
}
|
||||
|
||||
/** @var Form $form We need a form implementation for the next calls */
|
||||
if (!$form instanceof Form) {
|
||||
throw new RuntimeException('Form is not an instance of Form, so we cannot retrieve the clicked button!');
|
||||
}
|
||||
|
||||
//Remove the avatar attachment from the user if requested
|
||||
if ($form->getClickedButton() && 'remove_avatar' === $form->getClickedButton()->getName() && $user->getMasterPictureAttachment() instanceof Attachment) {
|
||||
$em->remove($user->getMasterPictureAttachment());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue