diff --git a/composer.json b/composer.json index c725b235..3b2904d6 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "api-platform/core": "^3.1", "beberlei/doctrineextensions": "^1.2", "brick/math": "0.12.1 as 0.11.0", - "composer/ca-bundle": "^1.5", + "composer/ca-bundle": "^1.3", "composer/package-versions-deprecated": "^1.11.99.5", "doctrine/data-fixtures": "^2.0.0", "doctrine/dbal": "^4.0.0", diff --git a/composer.lock b/composer.lock index 12abd82f..cf422f6b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7fb73581b0074c5a79afb3ffa614ed8e", + "content-hash": "75643d42e05fce4684644d375bff2d0a", "packages": [ { "name": "amphp/amp", diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index 78db06f0..c66b5fd9 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -156,10 +156,8 @@ class EntityURLGenerator public function viewURL(Attachment $entity): string { - //If the underlying file path is invalid, null gets returned, which is not allowed here. - //We still have the chance to use an external path, if it is set. - if ($entity->hasInternal() && ($url = $this->attachmentURLGenerator->getInternalViewURL($entity)) !== null) { - return $url; + if ($entity->hasInternal()) { + return $this->attachmentURLGenerator->getInternalViewURL($entity); } if($entity->hasExternal()) { diff --git a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php index b20368ce..d8e93321 100644 --- a/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php +++ b/src/Services/InfoProviderSystem/Providers/DigikeyProvider.php @@ -108,15 +108,12 @@ class DigikeyProvider implements InfoProviderInterface { $request = [ 'Keywords' => $keyword, - 'Limit' => 50, - 'Offset' => 0, - 'FilterOptionsRequest' => [ - 'MarketPlaceFilter' => 'ExcludeMarketPlace', - ], + 'RecordCount' => 50, + 'RecordStartPosition' => 0, + 'ExcludeMarketPlaceProducts' => 'true', ]; - //$response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [ - $response = $this->digikeyClient->request('POST', '/products/v4/search/keyword', [ + $response = $this->digikeyClient->request('POST', '/Search/v3/Products/Keyword', [ 'json' => $request, 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) ]); @@ -127,21 +124,18 @@ class DigikeyProvider implements InfoProviderInterface $result = []; $products = $response_array['Products']; foreach ($products as $product) { - foreach ($product['ProductVariations'] as $variation) { - $result[] = new SearchResultDTO( - provider_key: $this->getProviderKey(), - provider_id: $variation['DigiKeyProductNumber'], - name: $product['ManufacturerProductNumber'], - description: $product['Description']['DetailedDescription'] ?? $product['Description']['ProductDescription'], - category: $this->getCategoryString($product), - manufacturer: $product['Manufacturer']['Name'] ?? null, - mpn: $product['ManufacturerProductNumber'], - preview_image_url: $product['PhotoUrl'] ?? null, - manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']['Id']), - provider_url: $product['ProductUrl'], - footprint: $variation['PackageType']['Name'], //Use the footprint field, to show the user the package type (Tape & Reel, etc., as digikey has many different package types) - ); - } + $result[] = new SearchResultDTO( + provider_key: $this->getProviderKey(), + provider_id: $product['DigiKeyPartNumber'], + name: $product['ManufacturerPartNumber'], + description: $product['DetailedDescription'] ?? $product['ProductDescription'], + category: $this->getCategoryString($product), + manufacturer: $product['Manufacturer']['Value'] ?? null, + mpn: $product['ManufacturerPartNumber'], + preview_image_url: $product['PrimaryPhoto'] ?? null, + manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']), + provider_url: $product['ProductUrl'], + ); } return $result; @@ -149,79 +143,62 @@ class DigikeyProvider implements InfoProviderInterface public function getDetails(string $id): PartDetailDTO { - $response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/productdetails', [ + $response = $this->digikeyClient->request('GET', '/Search/v3/Products/' . urlencode($id), [ 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) ]); - $response_array = $response->toArray(); - $product = $response_array['Product']; + $product = $response->toArray(); $footprint = null; $parameters = $this->parametersToDTOs($product['Parameters'] ?? [], $footprint); - $media = $this->mediaToDTOs($id); - - // Get the price_breaks of the selected variation - $price_breaks = []; - foreach ($product['ProductVariations'] as $variation) { - if ($variation['DigiKeyProductNumber'] == $id) { - $price_breaks = $variation['StandardPricing'] ?? []; - break; - } - } + $media = $this->mediaToDTOs($product['MediaLinks']); return new PartDetailDTO( provider_key: $this->getProviderKey(), - provider_id: $id, - name: $product['ManufacturerProductNumber'], - description: $product['Description']['DetailedDescription'] ?? $product['Description']['ProductDescription'], + provider_id: $product['DigiKeyPartNumber'], + name: $product['ManufacturerPartNumber'], + description: $product['DetailedDescription'] ?? $product['ProductDescription'], category: $this->getCategoryString($product), - manufacturer: $product['Manufacturer']['Name'] ?? null, - mpn: $product['ManufacturerProductNumber'], - preview_image_url: $product['PhotoUrl'] ?? null, - manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']['Id']), + manufacturer: $product['Manufacturer']['Value'] ?? null, + mpn: $product['ManufacturerPartNumber'], + preview_image_url: $product['PrimaryPhoto'] ?? null, + manufacturing_status: $this->productStatusToManufacturingStatus($product['ProductStatus']), provider_url: $product['ProductUrl'], footprint: $footprint, datasheets: $media['datasheets'], images: $media['images'], parameters: $parameters, - vendor_infos: $this->pricingToDTOs($price_breaks, $id, $product['ProductUrl']), + vendor_infos: $this->pricingToDTOs($product['StandardPricing'] ?? [], $product['DigiKeyPartNumber'], $product['ProductUrl']), ); } /** * Converts the product status from the Digikey API to the manufacturing status used in Part-DB - * @param int|null $dk_status + * @param string|null $dk_status * @return ManufacturingStatus|null */ - private function productStatusToManufacturingStatus(?int $dk_status): ?ManufacturingStatus + private function productStatusToManufacturingStatus(?string $dk_status): ?ManufacturingStatus { - // The V4 can use strings to get the status, but if you have changed the PROVIDER_DIGIKEY_LANGUAGE it will not match. - // Using the Id instead which should be fixed. - // - // The API is not well documented and the ID are not there yet, so were extracted using "trial and error". - // The 'Preliminary' id was not found in several categories so I was unable to extract it. Disabled for now. return match ($dk_status) { null => null, - 0 => ManufacturingStatus::ACTIVE, - 1 => ManufacturingStatus::DISCONTINUED, - 2, 4 => ManufacturingStatus::EOL, - 7 => ManufacturingStatus::NRFND, - //'Preliminary' => ManufacturingStatus::ANNOUNCED, + 'Active' => ManufacturingStatus::ACTIVE, + 'Obsolete' => ManufacturingStatus::DISCONTINUED, + 'Discontinued at Digi-Key', 'Last Time Buy' => ManufacturingStatus::EOL, + 'Not For New Designs' => ManufacturingStatus::NRFND, + 'Preliminary' => ManufacturingStatus::ANNOUNCED, default => ManufacturingStatus::NOT_SET, }; } private function getCategoryString(array $product): string { - $category = $product['Category']['Name']; - $sub_category = current($product['Category']['ChildCategories']); + $category = $product['Category']['Value']; + $sub_category = $product['Family']['Value']; - if ($sub_category) { - //Replace the ' - ' category separator with ' -> ' - $category = $category . ' -> ' . str_replace(' - ', ' -> ', $sub_category["Name"]); - } + //Replace the ' - ' category separator with ' -> ' + $sub_category = str_replace(' - ', ' -> ', $sub_category); - return $category; + return $category . ' -> ' . $sub_category; } /** @@ -238,18 +215,18 @@ class DigikeyProvider implements InfoProviderInterface foreach ($parameters as $parameter) { if ($parameter['ParameterId'] === 1291) { //Meaning "Manufacturer given footprint" - $footprint_name = $parameter['ValueText']; + $footprint_name = $parameter['Value']; } - if (in_array(trim((string) $parameter['ValueText']), ['', '-'], true)) { + if (in_array(trim((string) $parameter['Value']), ['', '-'], true)) { continue; } //If the parameter was marked as text only, then we do not try to parse it as a numerical value if (in_array($parameter['ParameterId'], self::TEXT_ONLY_PARAMETERS, true)) { - $results[] = new ParameterDTO(name: $parameter['ParameterText'], value_text: $parameter['ValueText']); + $results[] = new ParameterDTO(name: $parameter['Parameter'], value_text: $parameter['Value']); } else { //Otherwise try to parse it as a numerical value - $results[] = ParameterDTO::parseValueIncludingUnit($parameter['ParameterText'], $parameter['ValueText']); + $results[] = ParameterDTO::parseValueIncludingUnit($parameter['Parameter'], $parameter['Value']); } } @@ -277,22 +254,16 @@ class DigikeyProvider implements InfoProviderInterface } /** - * @param string $id The Digikey product number, to get the media for + * @param array $media_links * @return FileDTO[][] * @phpstan-return array */ - private function mediaToDTOs(string $id): array + private function mediaToDTOs(array $media_links): array { $datasheets = []; $images = []; - $response = $this->digikeyClient->request('GET', '/products/v4/search/' . urlencode($id) . '/media', [ - 'auth_bearer' => $this->authTokenManager->getAlwaysValidTokenString(self::OAUTH_APP_NAME) - ]); - - $media_array = $response->toArray(); - - foreach ($media_array['MediaLinks'] as $media_link) { + foreach ($media_links as $media_link) { $file = new FileDTO(url: $media_link['Url'], name: $media_link['Title']); switch ($media_link['MediaType']) { diff --git a/src/Services/InfoProviderSystem/Providers/Element14Provider.php b/src/Services/InfoProviderSystem/Providers/Element14Provider.php index b942b929..eb1d4675 100644 --- a/src/Services/InfoProviderSystem/Providers/Element14Provider.php +++ b/src/Services/InfoProviderSystem/Providers/Element14Provider.php @@ -29,7 +29,6 @@ use App\Services\InfoProviderSystem\DTOs\ParameterDTO; use App\Services\InfoProviderSystem\DTOs\PartDetailDTO; use App\Services\InfoProviderSystem\DTOs\PriceDTO; use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO; -use Composer\CaBundle\CaBundle; use Symfony\Contracts\HttpClient\HttpClientInterface; class Element14Provider implements InfoProviderInterface @@ -44,19 +43,9 @@ class Element14Provider implements InfoProviderInterface private const COMPLIANCE_ATTRIBUTES = ['euEccn', 'hazardous', 'MSL', 'productTraceability', 'rohsCompliant', 'rohsPhthalatesCompliant', 'SVHC', 'tariffCode', 'usEccn', 'hazardCode']; - private readonly HttpClientInterface $element14Client; - - public function __construct(HttpClientInterface $element14Client, private readonly string $api_key, private readonly string $store_id) + public function __construct(private readonly HttpClientInterface $element14Client, private readonly string $api_key, private readonly string $store_id) { - /* We use the mozilla CA from the composer ca bundle directly, as some debian systems seems to have problems - * with the SSL.COM CA, element14 uses. See https://github.com/Part-DB/Part-DB-server/issues/866 - * - * This is a workaround until the issue is resolved in debian (or never). - * As this only affects this provider, this should have no negative impact and the CA bundle is still secure. - */ - $this->element14Client = $element14Client->withOptions([ - 'cafile' => CaBundle::getBundledCaBundlePath(), - ]); + } public function getProviderInfo(): array diff --git a/src/Services/InfoProviderSystem/Providers/MouserProvider.php b/src/Services/InfoProviderSystem/Providers/MouserProvider.php index 90bad263..c36fab66 100644 --- a/src/Services/InfoProviderSystem/Providers/MouserProvider.php +++ b/src/Services/InfoProviderSystem/Providers/MouserProvider.php @@ -94,7 +94,6 @@ class MouserProvider implements InfoProviderInterface From the startingRecord, the number of records specified will be returned up to the end of the recordset. This is useful for paging through the complete recordset of parts matching keyword. - searchOptions string Optional. If not provided, the default is None. @@ -177,16 +176,11 @@ class MouserProvider implements InfoProviderInterface throw new \RuntimeException('No part found with ID '.$id); } - //Manually filter out the part with the correct ID - $tmp = array_filter($tmp, fn(PartDetailDTO $part) => $part->provider_id === $id); - if (count($tmp) === 0) { - throw new \RuntimeException('No part found with ID '.$id); - } if (count($tmp) > 1) { - throw new \RuntimeException('Multiple parts found with ID '.$id); + throw new \RuntimeException('Multiple parts found with ID '.$id . ' ('.count($tmp).' found). This is basically a bug in Mousers API response. See issue #616.'); } - return reset($tmp); + return $tmp[0]; } public function getCapabilities(): array diff --git a/templates/base.html.twig b/templates/base.html.twig index e7c641c6..3b4cebba 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -38,7 +38,7 @@ - {% if not app.user.theme is defined or app.user.theme is null %} + {% if not app.user.theme is defined %} {% set theme = global_theme %} {% else %} {% set theme = app.user.theme %}