2023-07-09 14:27:41 +02:00
< ? php
/*
* This file is part of Part - DB ( https :// github . com / Part - DB / Part - DB - symfony ) .
*
* Copyright ( C ) 2019 - 2023 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 />.
*/
declare ( strict_types = 1 );
namespace App\Controller ;
2024-12-31 18:03:36 +01:00
use App\Entity\Parts\Manufacturer ;
2023-11-24 19:28:30 +01:00
use App\Entity\Parts\Part ;
2025-10-19 15:45:48 +02:00
use App\Exceptions\OAuthReconnectRequiredException ;
2023-07-09 17:55:41 +02:00
use App\Form\InfoProviderSystem\PartSearchType ;
2024-12-31 18:03:36 +01:00
use App\Services\InfoProviderSystem\ExistingPartFinder ;
2023-07-09 17:55:41 +02:00
use App\Services\InfoProviderSystem\PartInfoRetriever ;
2023-07-09 14:27:41 +02:00
use App\Services\InfoProviderSystem\ProviderRegistry ;
2025-08-24 23:32:58 +02:00
use App\Settings\AppSettings ;
2025-09-07 20:42:33 +02:00
use App\Settings\InfoProviderSystem\InfoProviderGeneralSettings ;
2024-12-31 18:03:36 +01:00
use Doctrine\ORM\EntityManagerInterface ;
2025-08-24 23:32:58 +02:00
use Jbtronics\SettingsBundle\Form\SettingsFormFactoryInterface ;
use Jbtronics\SettingsBundle\Manager\SettingsManagerInterface ;
2023-11-27 23:17:20 +01:00
use Psr\Log\LoggerInterface ;
2023-11-24 19:28:30 +01:00
use Symfony\Bridge\Doctrine\Attribute\MapEntity ;
2023-07-09 14:27:41 +02:00
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController ;
2025-08-24 23:32:58 +02:00
use Symfony\Component\Form\Extension\Core\Type\SubmitType ;
2023-11-27 23:17:20 +01:00
use Symfony\Component\HttpClient\Exception\ClientException ;
2023-07-09 17:55:41 +02:00
use Symfony\Component\HttpFoundation\Request ;
2023-07-09 14:27:41 +02:00
use Symfony\Component\HttpFoundation\Response ;
2024-03-03 20:37:33 +01:00
use Symfony\Component\Routing\Attribute\Route ;
2023-07-09 14:27:41 +02:00
2023-11-27 23:17:20 +01:00
use function Symfony\Component\Translation\t ;
2023-07-09 14:27:41 +02:00
#[Route('/tools/info_providers')]
class InfoProviderController extends AbstractController
{
2023-07-09 23:31:40 +02:00
public function __construct ( private readonly ProviderRegistry $providerRegistry ,
2024-12-31 18:03:36 +01:00
private readonly PartInfoRetriever $infoRetriever ,
2025-08-24 23:32:58 +02:00
private readonly ExistingPartFinder $existingPartFinder ,
private readonly SettingsManagerInterface $settingsManager ,
private readonly SettingsFormFactoryInterface $settingsFormFactory
2024-12-31 18:03:36 +01:00
)
2023-07-09 23:31:40 +02:00
{
}
2023-07-09 14:27:41 +02:00
#[Route('/providers', name: 'info_providers_list')]
2023-07-09 23:31:40 +02:00
public function listProviders () : Response
2023-07-09 14:27:41 +02:00
{
2023-07-16 20:33:24 +02:00
$this -> denyAccessUnlessGranted ( '@info_providers.create_parts' );
2023-07-09 14:27:41 +02:00
return $this -> render ( 'info_providers/providers_list/providers_list.html.twig' , [
2023-07-09 23:31:40 +02:00
'active_providers' => $this -> providerRegistry -> getActiveProviders (),
'disabled_providers' => $this -> providerRegistry -> getDisabledProviders (),
2023-07-09 14:27:41 +02:00
]);
}
2025-08-24 23:32:58 +02:00
#[Route('/provider/{provider}/settings', name: 'info_providers_provider_settings')]
public function providerSettings ( string $provider , Request $request ) : Response
{
$this -> denyAccessUnlessGranted ( '@config.change_system_settings' );
$this -> denyAccessUnlessGranted ( '@info_providers.create_parts' );
$providerInstance = $this -> providerRegistry -> getProviderByKey ( $provider );
$settingsClass = $providerInstance -> getProviderInfo ()[ 'settings_class' ] ? ? throw new \LogicException ( 'Provider ' . $provider . ' does not have a settings class defined' );
//Create a clone of the settings object
$settings = $this -> settingsManager -> createTemporaryCopy ( $settingsClass );
//Create a form builder for the settings object
$builder = $this -> settingsFormFactory -> createSettingsFormBuilder ( $settings );
//Add a submit button to the form
$builder -> add ( 'submit' , SubmitType :: class , [ 'label' => 'save' ]);
//Create the form
$form = $builder -> getForm ();
$form -> handleRequest ( $request );
//If the form was submitted and is valid, save the settings
if ( $form -> isSubmitted () && $form -> isValid ()) {
$this -> settingsManager -> mergeTemporaryCopy ( $settings );
$this -> settingsManager -> save ( $settings );
$this -> addFlash ( 'success' , t ( 'settings.flash.saved' ));
}
if ( $form -> isSubmitted () && ! $form -> isValid ()) {
$this -> addFlash ( 'error' , t ( 'settings.flash.invalid' ));
}
//Render the form
return $this -> render ( 'info_providers/settings/provider_settings.html.twig' , [
'form' => $form ,
'info_provider_key' => $provider ,
'info_provider_info' => $providerInstance -> getProviderInfo (),
]);
}
2023-07-09 14:27:41 +02:00
#[Route('/search', name: 'info_providers_search')]
2023-11-24 19:28:30 +01:00
#[Route('/update/{target}', name: 'info_providers_update_part_search')]
2025-09-07 20:42:33 +02:00
public function search ( Request $request , #[MapEntity(id: 'target')] ?Part $update_target, LoggerInterface $exceptionLogger, InfoProviderGeneralSettings $infoProviderSettings): Response
2023-07-09 14:27:41 +02:00
{
2023-07-16 20:33:24 +02:00
$this -> denyAccessUnlessGranted ( '@info_providers.create_parts' );
2023-07-09 17:55:41 +02:00
$form = $this -> createForm ( PartSearchType :: class );
$form -> handleRequest ( $request );
2023-07-09 14:27:41 +02:00
2023-07-09 17:55:41 +02:00
$results = null ;
2023-11-24 19:28:30 +01:00
//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 ()) {
2024-12-31 18:13:15 +01:00
//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
}
}
2023-11-24 19:28:30 +01:00
}
2025-09-07 20:42:33 +02:00
//If the providers form is still empty, use our default value from the settings
if ( count ( $form -> get ( 'providers' ) -> getData () ? ? []) === 0 ) {
$default_providers = $infoProviderSettings -> defaultSearchProviders ;
$provider_objects = [];
foreach ( $default_providers as $provider_key ) {
try {
$tmp = $this -> providerRegistry -> getProviderByKey ( $provider_key );
if ( $tmp -> isActive ()) {
$provider_objects [] = $tmp ;
}
} catch ( \InvalidArgumentException $e ) {
//If the provider is not found, just ignore it
}
}
$form -> get ( 'providers' ) -> setData ( $provider_objects );
}
2023-07-09 17:55:41 +02:00
if ( $form -> isSubmitted () && $form -> isValid ()) {
$keyword = $form -> get ( 'keyword' ) -> getData ();
$providers = $form -> get ( 'providers' ) -> getData ();
2024-12-31 18:03:36 +01:00
$dtos = [];
2023-11-27 23:17:20 +01:00
try {
2024-12-31 18:03:36 +01:00
$dtos = $this -> infoRetriever -> searchByKeyword ( keyword : $keyword , providers : $providers );
2023-11-27 23:17:20 +01:00
} 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 ]);
2025-10-19 15:45:48 +02:00
} catch ( OAuthReconnectRequiredException $e ) {
$this -> addFlash ( 'error' , t ( 'info_providers.search.error.oauth_reconnect' , [ '%provider%' => $e -> getProviderName ()]));
2023-11-27 23:17:20 +01:00
}
2024-12-31 18:03:36 +01:00
2025-10-19 15:45:48 +02:00
2024-12-31 18:03:36 +01:00
// 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' ]);
}
}
2023-07-09 17:55:41 +02:00
}
return $this -> render ( 'info_providers/search/part_search.html.twig' , [
'form' => $form ,
'results' => $results ,
2023-11-24 19:28:30 +01:00
'update_target' => $update_target
2023-07-09 17:55:41 +02:00
]);
2023-07-09 14:27:41 +02:00
}
2025-08-24 23:32:58 +02:00
}