2020-04-24 22:10:49 +02:00
< ? php
2022-11-29 21:21:26 +01:00
/*
* This file is part of Part - DB ( https :// github . com / Part - DB / Part - DB - symfony ) .
*
* Copyright ( C ) 2019 - 2022 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 />.
*/
2020-05-10 21:39:31 +02:00
declare ( strict_types = 1 );
2020-04-24 22:10:49 +02:00
/**
* This file is part of Part - DB ( https :// github . com / Part - DB / Part - DB - symfony ) .
*
2022-11-29 22:28:53 +01:00
* Copyright ( C ) 2019 - 2022 Jan Böhmer ( https :// github . com / jbtronics )
2020-04-24 22:10:49 +02:00
*
* 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\Controller ;
2026-02-23 09:26:44 +13:00
use App\Exceptions\InfoProviderNotActiveException ;
2020-04-26 21:26:11 +02:00
use App\Form\LabelSystem\ScanDialogType ;
2026-02-23 09:26:44 +13:00
use App\Services\InfoProviderSystem\Providers\LCSCProvider ;
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultHandler ;
2025-01-04 01:20:51 +01:00
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanHelper ;
2026-02-23 09:26:44 +13:00
use App\Services\LabelSystem\BarcodeScanner\BarcodeScanResultInterface ;
2025-01-04 01:20:51 +01:00
use App\Services\LabelSystem\BarcodeScanner\BarcodeSourceType ;
use App\Services\LabelSystem\BarcodeScanner\LocalBarcodeScanResult ;
2026-02-23 09:26:44 +13:00
use App\Services\LabelSystem\BarcodeScanner\LCSCBarcodeScanResult ;
use App\Services\LabelSystem\BarcodeScanner\EIGP114BarcodeScanResult ;
2020-04-26 18:59:49 +02:00
use Doctrine\ORM\EntityNotFoundException ;
2022-08-14 19:32:53 +02:00
use InvalidArgumentException ;
2020-04-24 22:10:49 +02:00
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController ;
2020-04-26 21:26:11 +02:00
use Symfony\Component\HttpFoundation\Request ;
use Symfony\Component\HttpFoundation\Response ;
2023-07-02 14:16:32 +02:00
use Symfony\Component\HttpKernel\Attribute\MapQueryParameter ;
2024-03-03 20:37:33 +01:00
use Symfony\Component\Routing\Attribute\Route ;
2026-02-23 09:26:44 +13:00
use App\Services\InfoProviderSystem\PartInfoRetriever ;
use App\Services\InfoProviderSystem\ProviderRegistry ;
use Symfony\Component\HttpFoundation\JsonResponse ;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface ;
use App\Entity\Parts\Part ;
use \App\Entity\Parts\StorageLocation ;
use Symfony\UX\Turbo\TurboBundle ;
2020-04-24 22:10:49 +02:00
2024-06-22 00:31:43 +02:00
/**
* @ see \App\Tests\Controller\ScanControllerTest
*/
2023-05-28 01:21:05 +02:00
#[Route(path: '/scan')]
2020-04-24 22:10:49 +02:00
class ScanController extends AbstractController
{
2026-02-23 09:26:44 +13:00
public function __construct (
protected BarcodeScanResultHandler $resultHandler ,
protected BarcodeScanHelper $barcodeNormalizer ,
) {}
2020-04-26 21:26:11 +02:00
2023-07-02 17:46:09 +02:00
#[Route(path: '', name: 'scan_dialog')]
2023-07-02 14:16:32 +02:00
public function dialog ( Request $request , #[MapQueryParameter] ?string $input = null): Response
2020-04-26 21:26:11 +02:00
{
2020-05-08 12:50:44 +02:00
$this -> denyAccessUnlessGranted ( '@tools.label_scanner' );
2020-04-26 21:26:11 +02:00
$form = $this -> createForm ( ScanDialogType :: class );
$form -> handleRequest ( $request );
2026-02-23 09:26:44 +13:00
// If JS is working, scanning uses /scan/lookup and this action just renders the page.
// This fallback only runs if user submits the form manually or uses ?input=...
2023-07-02 14:16:32 +02:00
if ( $input === null && $form -> isSubmitted () && $form -> isValid ()) {
2020-04-26 21:26:11 +02:00
$input = $form [ 'input' ] -> getData ();
2023-07-02 14:16:32 +02:00
}
2020-05-10 21:39:31 +02:00
2025-01-04 01:20:51 +01:00
2026-02-23 09:26:44 +13:00
if ( $input !== null && $input !== '' ) {
$mode = $form -> isSubmitted () ? $form [ 'mode' ] -> getData () : null ;
$infoMode = $form -> isSubmitted () && $form [ 'info_mode' ] -> getData ();
2020-04-26 21:26:11 +02:00
try {
2026-02-23 09:26:44 +13:00
$scan = $this -> barcodeNormalizer -> scanBarcodeContent ( $input , $mode ? ? null );
// If not in info mode, mimic “normal scan” behavior: redirect if possible.
if ( ! $infoMode ) {
// Try to get an Info URL if possible
$url = $this -> resultHandler -> getInfoURL ( $scan );
if ( $url !== null ) {
return $this -> redirect ( $url );
}
//Try to get an creation URL if possible (only for vendor codes)
$createUrl = $this -> buildCreateUrlForScanResult ( $scan );
if ( $createUrl !== null ) {
return $this -> redirect ( $createUrl );
}
//// Otherwise: show “not found” (not “format unknown”)
$this -> addFlash ( 'warning' , 'scan.qr_not_found' );
} else { // Info mode
// Info mode fallback: render page with prefilled result
$decoded = $scan -> getDecodedForInfoMode ();
//Try to resolve to an entity, to enhance info mode with entity-specific data
$dbEntity = $this -> resultHandler -> resolveEntity ( $scan );
$resolvedPart = $this -> resultHandler -> resolvePart ( $scan );
$openUrl = $this -> resultHandler -> getInfoURL ( $scan );
//If no entity is found, try to create an URL for creating a new part (only for vendor codes)
$createUrl = null ;
if ( $dbEntity === null ) {
$createUrl = $this -> buildCreateUrlForScanResult ( $scan );
}
if ( TurboBundle :: STREAM_FORMAT === $request -> getPreferredFormat ()) {
$request -> setRequestFormat ( TurboBundle :: STREAM_FORMAT );
return $this -> renderBlock ( 'label_system/scanner/scanner.html.twig' , 'scan_results' , [
'decoded' => $decoded ,
'entity' => $dbEntity ,
'part' => $resolvedPart ,
'openUrl' => $openUrl ,
'createUrl' => $createUrl ,
]);
2025-01-04 01:20:51 +01:00
}
2020-04-26 21:26:11 +02:00
}
2026-02-23 09:26:44 +13:00
} catch ( \Throwable $e ) {
// Keep fallback user-friendly; avoid 500
$this -> addFlash ( 'warning' , 'scan.format_unknown' );
2020-04-26 21:26:11 +02:00
}
}
2026-02-23 09:26:44 +13:00
//When we reach here, only the flash messages are relevant, so if it's a Turbo request, only send the flash message fragment, so the client can show it without a full page reload
if ( TurboBundle :: STREAM_FORMAT === $request -> getPreferredFormat ()) {
$request -> setRequestFormat ( TurboBundle :: STREAM_FORMAT );
//Only send our flash message, so the client can show it without a full page reload
return $this -> renderBlock ( '_turbo_control.html.twig' , 'flashes' );
}
2023-05-28 01:21:05 +02:00
return $this -> render ( 'label_system/scanner/scanner.html.twig' , [
2022-03-04 21:20:18 +01:00
'form' => $form ,
2026-02-23 09:26:44 +13:00
//Info mode
'decoded' => $decoded ? ? null ,
'entity' => $dbEntity ? ? null ,
'part' => $resolvedPart ? ? null ,
'openUrl' => $openUrl ? ? null ,
'createUrl' => $createUrl ? ? null ,
2020-04-26 21:26:11 +02:00
]);
2020-04-26 18:59:49 +02:00
}
2020-04-24 22:10:49 +02:00
/**
2020-05-10 21:39:31 +02:00
* The route definition for this action is done in routes . yaml , as it does not use the _locale prefix as the other routes .
2020-04-24 22:10:49 +02:00
*/
2020-04-26 21:26:11 +02:00
public function scanQRCode ( string $type , int $id ) : Response
2020-04-24 22:10:49 +02:00
{
2023-10-26 22:23:43 +02:00
$type = strtolower ( $type );
2020-04-26 18:59:49 +02:00
try {
$this -> addFlash ( 'success' , 'scan.qr_success' );
2020-05-10 21:39:31 +02:00
2023-10-26 22:23:43 +02:00
if ( ! isset ( BarcodeScanHelper :: QR_TYPE_MAP [ $type ])) {
throw new InvalidArgumentException ( 'Unknown type: ' . $type );
}
//Construct the scan result manually, as we don't have a barcode here
2025-01-04 01:20:51 +01:00
$scan_result = new LocalBarcodeScanResult (
2023-10-26 22:23:43 +02:00
target_type : BarcodeScanHelper :: QR_TYPE_MAP [ $type ],
target_id : $id ,
//The routes are only used on the internal generated QR codes
source_type : BarcodeSourceType :: INTERNAL
);
2026-02-23 09:26:44 +13:00
return $this -> redirect ( $this -> resultHandler -> getInfoURL ( $scan_result ) ? ? throw new EntityNotFoundException ( " Not found " ));
2023-06-11 14:15:46 +02:00
} catch ( EntityNotFoundException ) {
2020-04-26 18:59:49 +02:00
$this -> addFlash ( 'success' , 'scan.qr_not_found' );
2020-05-10 21:39:31 +02:00
2020-04-26 18:59:49 +02:00
return $this -> redirectToRoute ( 'homepage' );
}
2020-04-24 22:10:49 +02:00
}
2026-02-23 09:26:44 +13:00
/**
* Builds a URL for creating a new part based on the barcode data , handles exceptions and shows user - friendly error messages if the provider is not active or if there is an error during URL generation .
* @ param BarcodeScanResultInterface $scanResult
* @ return string | null
*/
private function buildCreateUrlForScanResult ( BarcodeScanResultInterface $scanResult ) : ? string
{
try {
return $this -> resultHandler -> getCreationURL ( $scanResult );
} catch ( InfoProviderNotActiveException $e ) {
$this -> addFlash ( 'error' , $e -> getMessage ());
} catch ( \Throwable ) {
// Don’ t break scanning UX if provider lookup fails
$this -> addFlash ( 'error' , 'An error occurred while looking up the provider for this barcode. Please try again later.' );
}
return null ;
}
2020-05-10 21:39:31 +02:00
}