. */ declare(strict_types=1); namespace App\Controller; use App\DataTables\ErrorDataTable; use App\DataTables\Filters\PartFilter; use App\DataTables\Filters\PartSearchFilter; use App\DataTables\PartsDataTable; use App\Entity\Parts\Category; use App\Entity\Parts\Footprint; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Storelocation; use App\Entity\Parts\Supplier; use App\Form\Filters\PartFilterType; use App\Services\Parts\PartsTableActionHandler; use App\Services\Trees\NodesListBuilder; use Doctrine\DBAL\Exception\DriverException; use Doctrine\ORM\EntityManagerInterface; use Omines\DataTablesBundle\DataTableFactory; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Symfony\Contracts\Translation\TranslatorInterface; class PartListsController extends AbstractController { private EntityManagerInterface $entityManager; private NodesListBuilder $nodesListBuilder; private DataTableFactory $dataTableFactory; private TranslatorInterface $translator; public function __construct(EntityManagerInterface $entityManager, NodesListBuilder $nodesListBuilder, DataTableFactory $dataTableFactory, TranslatorInterface $translator) { $this->entityManager = $entityManager; $this->nodesListBuilder = $nodesListBuilder; $this->dataTableFactory = $dataTableFactory; $this->translator = $translator; } /** * @Route("/table/action", name="table_action", methods={"POST"}) */ public function tableAction(Request $request, PartsTableActionHandler $actionHandler): Response { $this->denyAccessUnlessGranted('@parts.edit'); $redirect = $request->request->get('redirect_back'); $ids = $request->request->get('ids'); $action = $request->request->get('action'); $target = $request->request->get('target'); if (!$this->isCsrfTokenValid('table_action', $request->request->get('_token'))) { $this->addFlash('error', 'csfr_invalid'); return $this->redirect($redirect); } if (null === $action || null === $ids) { $this->addFlash('error', 'part.table.actions.no_params_given'); } else { $parts = $actionHandler->idStringToArray($ids); $redirectResponse = $actionHandler->handleAction($action, $parts, $target ? (int) $target : null, $redirect); //Save changes $this->entityManager->flush(); $this->addFlash('success', 'part.table.actions.success'); } //If the action handler returned a response, we use it, otherwise we redirect back to the previous page. if (isset($redirectResponse) && $redirectResponse instanceof Response) { return $redirectResponse; } return $this->redirect($redirect); } /** * Disable the given form interface after creation of the form by removing and reattaching the form. * @param FormInterface $form * @return void */ private function disableFormFieldAfterCreation(FormInterface $form, bool $disabled = true): void { $attrs = $form->getConfig()->getOptions(); $attrs['disabled'] = $disabled; $parent = $form->getParent(); if ($parent === null) { throw new \RuntimeException('This function can only be used on form fields that are children of another form!'); } $parent->remove($form->getName()); $parent->add($form->getName(), get_class($form->getConfig()->getType()->getInnerType()), $attrs); } /** * Common implementation for the part list pages. * @param Request $request The request to parse * @param string $template The template that should be rendered * @param callable|null $filter_changer A function that is called with the filter object as parameter. This function can be used to customize the filter * @param callable|null $form_changer A function that is called with the form object as parameter. This function can be used to customize the form * @param array $additonal_template_vars Any additional template variables that should be passed to the template * @param array $additional_table_vars Any additional variables that should be passed to the table creation * @return Response */ protected function showListWithFilter(Request $request, string $template, ?callable $filter_changer = null, ?callable $form_changer = null, array $additonal_template_vars = [], array $additional_table_vars = []): Response { $this->denyAccessUnlessGranted('@parts.read'); $formRequest = clone $request; $formRequest->setMethod('GET'); $filter = new PartFilter($this->nodesListBuilder); if($filter_changer !== null){ $filter_changer($filter); } $filterForm = $this->createForm(PartFilterType::class, $filter, ['method' => 'GET']); if($form_changer !== null) { $form_changer($filterForm); } $filterForm->handleRequest($formRequest); $table = $this->dataTableFactory->createFromType(PartsDataTable::class, array_merge(['filter' => $filter], $additional_table_vars)) ->handleRequest($request); if ($table->isCallback()) { try { return $table->getResponse(); } catch (DriverException $driverException) { if ($driverException->getCode() === 1139) { //Show only the part after "1139" $regex_message = preg_replace('/^.*1139 /', '', $driverException->getMessage()); $errors = $this->translator->trans('part.table.invalid_regex') . ': ' . $regex_message; return ErrorDataTable::errorTable($this->dataTableFactory, $request, $errors); } else { throw $driverException; } } } return $this->render($template, array_merge([ 'datatable' => $table, 'filterForm' => $filterForm->createView(), ], $additonal_template_vars)); } /** * @Route("/category/{id}/parts", name="part_list_category") * * @return JsonResponse|Response */ public function showCategory(Category $category, Request $request): Response { $this->denyAccessUnlessGranted('@categories.read'); return $this->showListWithFilter($request, 'parts/lists/category_list.html.twig', function (PartFilter $filter) use ($category) { $filter->getCategory()->setOperator('INCLUDING_CHILDREN')->setValue($category); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('category')->get('value')); }, [ 'entity' => $category, 'repo' => $this->entityManager->getRepository(Category::class), ] ); } /** * @Route("/footprint/{id}/parts", name="part_list_footprint") * * @return JsonResponse|Response */ public function showFootprint(Footprint $footprint, Request $request): Response { $this->denyAccessUnlessGranted('@footprints.read'); return $this->showListWithFilter($request, 'parts/lists/footprint_list.html.twig', function (PartFilter $filter) use ($footprint) { $filter->getFootprint()->setOperator('INCLUDING_CHILDREN')->setValue($footprint); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('footprint')->get('value')); }, [ 'entity' => $footprint, 'repo' => $this->entityManager->getRepository(Footprint::class), ] ); } /** * @Route("/manufacturer/{id}/parts", name="part_list_manufacturer") * * @return JsonResponse|Response */ public function showManufacturer(Manufacturer $manufacturer, Request $request): Response { $this->denyAccessUnlessGranted('@manufacturers.read'); return $this->showListWithFilter($request, 'parts/lists/manufacturer_list.html.twig', function (PartFilter $filter) use ($manufacturer) { $filter->getManufacturer()->setOperator('INCLUDING_CHILDREN')->setValue($manufacturer); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('manufacturer')->get('value')); }, [ 'entity' => $manufacturer, 'repo' => $this->entityManager->getRepository(Manufacturer::class), ] ); } /** * @Route("/store_location/{id}/parts", name="part_list_store_location") * * @return JsonResponse|Response */ public function showStorelocation(Storelocation $storelocation, Request $request): Response { $this->denyAccessUnlessGranted('@storelocations.read'); return $this->showListWithFilter($request, 'parts/lists/store_location_list.html.twig', function (PartFilter $filter) use ($storelocation) { $filter->getStorelocation()->setOperator('INCLUDING_CHILDREN')->setValue($storelocation); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('storelocation')->get('value')); }, [ 'entity' => $storelocation, 'repo' => $this->entityManager->getRepository(Storelocation::class), ] ); } /** * @Route("/supplier/{id}/parts", name="part_list_supplier") * * @return JsonResponse|Response */ public function showSupplier(Supplier $supplier, Request $request): Response { $this->denyAccessUnlessGranted('@suppliers.read'); return $this->showListWithFilter($request, 'parts/lists/supplier_list.html.twig', function (PartFilter $filter) use ($supplier) { $filter->getSupplier()->setOperator('INCLUDING_CHILDREN')->setValue($supplier); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('supplier')->get('value')); }, [ 'entity' => $supplier, 'repo' => $this->entityManager->getRepository(Supplier::class), ] ); } /** * @Route("/parts/by_tag/{tag}", name="part_list_tags", requirements={"tag": ".*"}) * * @return JsonResponse|Response */ public function showTag(string $tag, Request $request): Response { $tag = trim($tag); return $this->showListWithFilter($request, 'parts/lists/tags_list.html.twig', function (PartFilter $filter) use ($tag) { $filter->getTags()->setOperator('ANY')->setValue($tag); }, function (FormInterface $filterForm) { $this->disableFormFieldAfterCreation($filterForm->get('tags')->get('value')); }, [ 'tag' => $tag, ] ); } private function searchRequestToFilter(Request $request): PartSearchFilter { $filter = new PartSearchFilter($request->query->get('keyword', '')); $filter->setName($request->query->getBoolean('name', true)); $filter->setCategory($request->query->getBoolean('category', true)); $filter->setDescription($request->query->getBoolean('description', true)); $filter->setMpn($request->query->getBoolean('mpn', true)); $filter->setTags($request->query->getBoolean('tags', true)); $filter->setStorelocation($request->query->getBoolean('storelocation', true)); $filter->setComment($request->query->getBoolean('comment', true)); $filter->setIPN($request->query->getBoolean('ipn', true)); $filter->setOrdernr($request->query->getBoolean('ordernr', true)); $filter->setSupplier($request->query->getBoolean('supplier', false)); $filter->setManufacturer($request->query->getBoolean('manufacturer', false)); $filter->setFootprint($request->query->getBoolean('footprint', false)); $filter->setRegex($request->query->getBoolean('regex', false)); return $filter; } /** * @Route("/parts/search", name="parts_search") * * @return JsonResponse|Response */ public function showSearch(Request $request, DataTableFactory $dataTable): Response { $searchFilter = $this->searchRequestToFilter($request); return $this->showListWithFilter($request, 'parts/lists/search_list.html.twig', null, null, [ 'keyword' => $searchFilter->getKeyword(), 'searchFilter' => $searchFilter, ], [ 'search' => $searchFilter, ] ); } /** * @Route("/parts", name="parts_show_all") * * @return Response */ public function showAll(Request $request): Response { return $this->showListWithFilter($request,'parts/lists/all_list.html.twig'); } }