<?php
namespace App\Controller\Content;
use App\Controller\BaseController;
use App\Helpers\Odbc\OdbcProdukte;
use App\Search\EntitySearchService;
use App\Service\IceCatService;
use App\Service\SearchResultsFormatter;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller handling downloads and entity search functionality.
*/
class DownloadController extends BaseController
{
/**
* @Route("/download", name="download")
*/
public function download(): Response
{
return $this->baseRender('content/downloads.html.twig');
}
/**
* @Route("/entity-search", name="entity_search")
*/
public function entitySearch(): Response
{
return $this->baseRender('content/entity_search.html.twig');
}
/**
* @Route("/search-entity-fast", name="search_entity_fast")
*
* Main search endpoint using the refactored EntitySearchService.
*/
public function searchEntityFast(
Request $request,
EntitySearchService $searchService,
SearchResultsFormatter $formatter
): JsonResponse {
$search = $request->query->get('search', '');
$category = $request->query->get('category');
$manufacturer = $request->query->get('manufacturer');
// Execute search
$response = $searchService->search($search, $category, $manufacturer);
$responseArray = $response->toArray();
// Handle device-related scenarios (no HTML formatting needed)
if ($response->getScenario()->isDeviceRelated()) {
return new JsonResponse($responseArray);
}
// Format results into HTML for other scenarios
$checkIceCat = $request->query->get('check_icecat', 'false') === 'true';
$formattedResults = $formatter->formatSearchResults($responseArray, $checkIceCat);
return new JsonResponse([
'html' => $formattedResults['html'],
'count' => $formattedResults['count'],
'scenario' => $responseArray['scenario'],
'metadata' => $responseArray['metadata'],
'icecat_status' => $formattedResults['icecat_status'] ?? [],
'results' => $responseArray['results'],
'alternatives' => $responseArray['alternatives'],
]);
}
/**
* @Route("/entity-search/categories", name="entity_search_categories")
*/
public function getCategories(EntitySearchService $searchService): JsonResponse
{
return new JsonResponse($searchService->getCategories());
}
/**
* @Route("/entity-search/manufacturers", name="entity_search_manufacturers")
*/
public function getManufacturers(EntitySearchService $searchService): JsonResponse
{
return new JsonResponse($searchService->getManufacturers());
}
/**
* @Route("/entity-search/supplies", name="entity_search_supplies")
*
* Get supplies/components for a device model with optional type filter.
*/
public function getSuppliesForModel(
Request $request,
EntitySearchService $searchService
): JsonResponse {
$modelId = $request->query->get('model_id');
$supplyType = $request->query->get('supply_type');
if (empty($modelId)) {
return new JsonResponse(['error' => 'model_id is required'], 400);
}
$response = $searchService->getSuppliesForModel(
(int)$modelId,
$supplyType !== null && $supplyType !== '' ? (int)$supplyType : null
);
if ($response->isError()) {
return new JsonResponse(['error' => $response->getErrorMessage()], 404);
}
$responseArray = $response->toArray();
// Build image URLs for supplies
$supplies = $responseArray['supplies'] ?? [];
foreach ($supplies as &$supply) {
$supply['image_url'] = $this->buildSupplyImageUrl($supply);
}
return new JsonResponse([
'model' => $responseArray['device'] ?? null,
'supply_types' => $responseArray['supply_types'] ?? [],
'supplies' => $supplies,
'count' => count($supplies),
'current_filter' => $supplyType !== null ? (int)$supplyType : null,
]);
}
/**
* @Route("/opcache-reset-temp", name="opcache_reset_temp")
*/
public function opcacheReset(): JsonResponse
{
if (function_exists('opcache_reset')) {
opcache_reset();
return new JsonResponse(['status' => 'OPcache reset successfully']);
}
return new JsonResponse(['status' => 'OPcache not available'], 500);
}
/**
* @Route("/product-details", name="product_details")
*
* Get detailed product information for modal display (includes IceCat data).
*/
public function getProductDetails(
Request $request,
ManagerRegistry $doctrine,
IceCatService $iceCatService,
EntitySearchService $searchService
): JsonResponse {
$articleNr = $request->query->get('article_nr', '');
$manufacturer = $request->query->get('manufacturer', '');
if (empty($articleNr)) {
return new JsonResponse(['error' => 'Article number is required'], 400);
}
try {
$productData = $this->fetchProductData($doctrine, $articleNr, $manufacturer);
if (!$productData) {
return new JsonResponse(['error' => 'Product not found'], 404);
}
// Add compatible devices
$this->addCompatibleDevices($productData, $articleNr, $searchService);
// Add IceCat data
$this->addIceCatData($productData, $iceCatService, $articleNr, $manufacturer);
return new JsonResponse($productData);
} catch (\Exception $e) {
error_log('Error fetching product details: ' . $e->getMessage());
return new JsonResponse(['error' => 'Failed to load product details'], 500);
}
}
// =========================================================================
// Private Helper Methods
// =========================================================================
private function buildSupplyImageUrl(array $supply): string
{
$imageFile = $supply['image_file'] ?? '';
$manufacturer = $supply['manufacturer'] ?? '';
if (empty($imageFile) || $imageFile === 'nopic.gif') {
return '/img/nopic.gif';
}
return '/image/' . strtoupper($manufacturer) . '/' . $imageFile;
}
private function fetchProductData(
ManagerRegistry $doctrine,
string $articleNr,
string $manufacturer
): ?array {
// Try ODBC first
try {
$odbc = new OdbcProdukte();
$products = $odbc->searchByArtnr($articleNr, 1);
if (!empty($products)) {
$product = $products[0];
return [
'article_nr' => $product->getArtnr(),
'manufacturer' => $product->getHersteller(),
'description' => $product->getDescription(),
'ean' => $product->getEan(),
'category' => $product->getArtGr(),
'image_file' => $product->getBildDatei(),
'price' => $product->getVk(),
'availability' => $product->getAvailability(),
'delivery' => $product->getDeliveryTime(),
'source' => 'odbc',
'image_url' => $this->buildProductImageUrl($product->getBildDatei(), $product->getHersteller()),
];
}
} catch (\Exception $e) {
error_log('ODBC not available: ' . $e->getMessage());
}
// Fallback to MySQL entity table
$connection = $doctrine->getConnection();
$sql = "
SELECT
entity_id,
name1 AS article_nr,
hersteller AS manufacturer,
name2 AS description,
ean,
artgr AS category,
picfile AS image_file,
description AS long_description
FROM entity
WHERE name1 = :article_nr
";
$params = ['article_nr' => $articleNr];
if ($manufacturer) {
$sql .= " AND hersteller = :manufacturer";
$params['manufacturer'] = $manufacturer;
}
$sql .= " LIMIT 1";
$stmt = $connection->prepare($sql);
$result = $stmt->executeQuery($params);
$row = $result->fetchAssociative();
if ($row) {
$row['source'] = 'mysql';
$row['image_url'] = $this->buildProductImageUrl($row['image_file'] ?? '', $row['manufacturer'] ?? '');
return $row;
}
// Fallback to supplies_models
$sql = "
SELECT
sm.model_id,
sm.artnr AS article_nr,
sb.brand_name AS manufacturer,
sm.model_name AS description,
sm.bild AS image_file
FROM supplies_models sm
LEFT JOIN supplies_brand sb ON sm.brand_id = sb.brand_id
WHERE sm.artnr = :article_nr
LIMIT 1
";
$stmt = $connection->prepare($sql);
$result = $stmt->executeQuery(['article_nr' => $articleNr]);
$row = $result->fetchAssociative();
if ($row) {
$row['source'] = 'supplies_models';
$row['category'] = 'Gerät/Drucker';
$row['image_url'] = !empty($row['image_file'])
? '/img/drucker/' . $row['image_file']
: '/img/nopic.gif';
return $row;
}
return null;
}
private function buildProductImageUrl(?string $imageFile, string $manufacturer): string
{
if (empty($imageFile) || $imageFile === 'nopic.gif') {
return '/img/nopic.gif';
}
return '/image/' . strtolower($manufacturer) . '/' . $imageFile;
}
private function addCompatibleDevices(
array &$productData,
string $articleNr,
EntitySearchService $searchService
): void {
// Use the supplies repository through the search service
// For now, we'll use direct query (TODO: expose through service)
$productData['compatible_with'] = '';
$productData['compatible_count'] = 0;
}
private function addIceCatData(
array &$productData,
IceCatService $iceCatService,
string $articleNr,
string $manufacturer
): void {
try {
$ean = $productData['ean'] ?? '';
$productManufacturer = $productData['manufacturer'] ?? $manufacturer;
// Handle HP special case
if (substr($productManufacturer, 0, 2) === 'HP') {
$productManufacturer = 'HP';
}
$iceCatResponse = $iceCatService->getProductSpecs($ean, $articleNr, $productManufacturer, true);
if ($iceCatResponse && $iceCatResponse['success']) {
$productData['icecat_available'] = true;
$productData['icecat_specs'] = $iceCatResponse['specs'];
$productData['icecat_image_url'] = $iceCatResponse['image_url'] ?? null;
$productData['icecat_details'] = [
'http_status_code' => $iceCatResponse['http_status'],
'request_url' => $iceCatResponse['request_url'],
'response_time_ms' => $iceCatResponse['response_time_ms'],
];
} else {
$productData['icecat_available'] = false;
$productData['icecat_details'] = [
'error' => $iceCatResponse['error'] ?? 'Product not found in IceCat',
];
}
} catch (\Exception $e) {
$productData['icecat_available'] = false;
$productData['icecat_details'] = ['error' => $e->getMessage()];
}
}
}