ver. 0.302: REST API product variants, attributes dictionary, attribute filtering
- Add variant CRUD endpoints (variants, create_variant, update_variant, delete_variant) - Add dictionaries/attributes endpoint with multilingual names and values - Add attribute_* filter params for product list filtering by attribute values - Enrich product detail attributes with translated names (attribute_names, value_names) - Include variants array in product detail response for parent products - Add price_brutto validation on product create - Batch-load attribute/value translations (4 queries instead of N+1) - Add 43 new unit tests (730 total, 2066 assertions) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -534,6 +534,127 @@ class AttributeRepository
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca aktywne atrybuty z wartościami i wielojęzycznymi nazwami dla REST API.
|
||||
*
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
public function listForApi(): array
|
||||
{
|
||||
// 1. Get all active attribute IDs (1 query)
|
||||
$rows = $this->db->select('pp_shop_attributes', ['id', 'type', 'status'], [
|
||||
'status' => 1,
|
||||
'ORDER' => ['o' => 'ASC'],
|
||||
]);
|
||||
if (!is_array($rows) || empty($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$attrIds = [];
|
||||
foreach ($rows as $row) {
|
||||
$id = (int)($row['id'] ?? 0);
|
||||
if ($id > 0) {
|
||||
$attrIds[] = $id;
|
||||
}
|
||||
}
|
||||
if (empty($attrIds)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 2. Batch load ALL attribute translations (1 query)
|
||||
$allAttrTranslations = $this->db->select(
|
||||
'pp_shop_attributes_langs',
|
||||
['attribute_id', 'lang_id', 'name'],
|
||||
['attribute_id' => $attrIds]
|
||||
);
|
||||
$attrNamesMap = [];
|
||||
if (is_array($allAttrTranslations)) {
|
||||
foreach ($allAttrTranslations as $t) {
|
||||
$aId = (int)($t['attribute_id'] ?? 0);
|
||||
$langId = (string)($t['lang_id'] ?? '');
|
||||
if ($aId > 0 && $langId !== '') {
|
||||
$attrNamesMap[$aId][$langId] = (string)($t['name'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Batch load ALL values for those attribute IDs (1 query)
|
||||
$allValueRows = $this->db->select(
|
||||
'pp_shop_attributes_values',
|
||||
['id', 'attribute_id', 'is_default', 'impact_on_the_price'],
|
||||
[
|
||||
'attribute_id' => $attrIds,
|
||||
'ORDER' => ['id' => 'ASC'],
|
||||
]
|
||||
);
|
||||
$valuesByAttr = [];
|
||||
$allValueIds = [];
|
||||
if (is_array($allValueRows)) {
|
||||
foreach ($allValueRows as $vRow) {
|
||||
$valueId = (int)($vRow['id'] ?? 0);
|
||||
$attrId = (int)($vRow['attribute_id'] ?? 0);
|
||||
if ($valueId > 0 && $attrId > 0) {
|
||||
$valuesByAttr[$attrId][] = $vRow;
|
||||
$allValueIds[] = $valueId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Batch load ALL value translations (1 query)
|
||||
$valueNamesMap = [];
|
||||
if (!empty($allValueIds)) {
|
||||
$allValueTranslations = $this->db->select(
|
||||
'pp_shop_attributes_values_langs',
|
||||
['value_id', 'lang_id', 'name'],
|
||||
['value_id' => $allValueIds]
|
||||
);
|
||||
if (is_array($allValueTranslations)) {
|
||||
foreach ($allValueTranslations as $vt) {
|
||||
$vId = (int)($vt['value_id'] ?? 0);
|
||||
$langId = (string)($vt['lang_id'] ?? '');
|
||||
if ($vId > 0 && $langId !== '') {
|
||||
$valueNamesMap[$vId][$langId] = (string)($vt['name'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Assemble result in-memory
|
||||
$result = [];
|
||||
foreach ($rows as $row) {
|
||||
$attributeId = (int)($row['id'] ?? 0);
|
||||
if ($attributeId <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$names = isset($attrNamesMap[$attributeId]) ? $attrNamesMap[$attributeId] : [];
|
||||
|
||||
$values = [];
|
||||
if (isset($valuesByAttr[$attributeId])) {
|
||||
foreach ($valuesByAttr[$attributeId] as $vRow) {
|
||||
$valueId = (int)$vRow['id'];
|
||||
$impact = $vRow['impact_on_the_price'];
|
||||
$values[] = [
|
||||
'id' => $valueId,
|
||||
'names' => isset($valueNamesMap[$valueId]) ? $valueNamesMap[$valueId] : [],
|
||||
'is_default' => (int)($vRow['is_default'] ?? 0),
|
||||
'impact_on_the_price' => ($impact !== null && $impact !== '') ? (float)$impact : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'id' => $attributeId,
|
||||
'type' => (int)($row['type'] ?? 0),
|
||||
'status' => (int)($row['status'] ?? 0),
|
||||
'names' => $names,
|
||||
'values' => $values,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{sql: string, params: array<string, mixed>}
|
||||
*/
|
||||
|
||||
@@ -508,6 +508,31 @@ class ProductRepository
|
||||
$params[':promoted'] = (int)$promotedFilter;
|
||||
}
|
||||
|
||||
// Attribute filters: attribute_{id} = {value_id}
|
||||
$attrFilters = isset($filters['attributes']) && is_array($filters['attributes']) ? $filters['attributes'] : [];
|
||||
$attrIdx = 0;
|
||||
foreach ($attrFilters as $attrId => $valueId) {
|
||||
$attrId = (int)$attrId;
|
||||
$valueId = (int)$valueId;
|
||||
if ($attrId <= 0 || $valueId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$paramAttr = ':attr_id_' . $attrIdx;
|
||||
$paramVal = ':attr_val_' . $attrIdx;
|
||||
$where[] = "EXISTS (
|
||||
SELECT 1
|
||||
FROM pp_shop_products AS psp_var{$attrIdx}
|
||||
INNER JOIN pp_shop_products_attributes AS pspa{$attrIdx}
|
||||
ON pspa{$attrIdx}.product_id = psp_var{$attrIdx}.id
|
||||
WHERE psp_var{$attrIdx}.parent_id = psp.id
|
||||
AND pspa{$attrIdx}.attribute_id = {$paramAttr}
|
||||
AND pspa{$attrIdx}.value_id = {$paramVal}
|
||||
)";
|
||||
$params[$paramAttr] = $attrId;
|
||||
$params[$paramVal] = $valueId;
|
||||
$attrIdx++;
|
||||
}
|
||||
|
||||
$whereSql = implode(' AND ', $where);
|
||||
|
||||
$sqlCount = "
|
||||
@@ -681,18 +706,413 @@ class ProductRepository
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes
|
||||
// Attributes (enriched with names) — batch-loaded
|
||||
$attributes = $this->db->select('pp_shop_products_attributes', ['attribute_id', 'value_id'], ['product_id' => $id]);
|
||||
$result['attributes'] = [];
|
||||
if (is_array($attributes)) {
|
||||
if (is_array($attributes) && !empty($attributes)) {
|
||||
$attrIds = [];
|
||||
$valueIds = [];
|
||||
foreach ($attributes as $attr) {
|
||||
$attrIds[] = (int)$attr['attribute_id'];
|
||||
$valueIds[] = (int)$attr['value_id'];
|
||||
}
|
||||
$attrNamesMap = $this->batchLoadAttributeNames($attrIds);
|
||||
$valueNamesMap = $this->batchLoadValueNames($valueIds);
|
||||
$attrTypesMap = $this->batchLoadAttributeTypes($attrIds);
|
||||
|
||||
foreach ($attributes as $attr) {
|
||||
$attrId = (int)$attr['attribute_id'];
|
||||
$valId = (int)$attr['value_id'];
|
||||
$result['attributes'][] = [
|
||||
'attribute_id' => (int)$attr['attribute_id'],
|
||||
'value_id' => (int)$attr['value_id'],
|
||||
'attribute_id' => $attrId,
|
||||
'attribute_type' => isset($attrTypesMap[$attrId]) ? $attrTypesMap[$attrId] : 0,
|
||||
'attribute_names' => isset($attrNamesMap[$attrId]) ? $attrNamesMap[$attrId] : [],
|
||||
'value_id' => $valId,
|
||||
'value_names' => isset($valueNamesMap[$valId]) ? $valueNamesMap[$valId] : [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Variants (only for parent products)
|
||||
if (empty($product['parent_id'])) {
|
||||
$result['variants'] = $this->findVariantsForApi($id);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera warianty produktu z atrybutami i tłumaczeniami dla REST API.
|
||||
*
|
||||
* @param int $productId ID produktu nadrzędnego
|
||||
* @return array Lista wariantów
|
||||
*/
|
||||
public function findVariantsForApi(int $productId): array
|
||||
{
|
||||
$stmt = $this->db->query(
|
||||
'SELECT id, permutation_hash, sku, ean, price_brutto, price_brutto_promo,
|
||||
price_netto, price_netto_promo, quantity, stock_0_buy, weight, status
|
||||
FROM pp_shop_products
|
||||
WHERE parent_id = :pid
|
||||
ORDER BY id ASC',
|
||||
[':pid' => $productId]
|
||||
);
|
||||
|
||||
$rows = $stmt ? $stmt->fetchAll(\PDO::FETCH_ASSOC) : [];
|
||||
if (!is_array($rows)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Collect all variant IDs, then load attributes in batch
|
||||
$variantIds = [];
|
||||
foreach ($rows as $row) {
|
||||
$variantIds[] = (int)$row['id'];
|
||||
}
|
||||
|
||||
// Load all attributes for all variants at once
|
||||
$allAttrsRaw = [];
|
||||
if (!empty($variantIds)) {
|
||||
$allAttrsRaw = $this->db->select('pp_shop_products_attributes', ['product_id', 'attribute_id', 'value_id'], ['product_id' => $variantIds]);
|
||||
if (!is_array($allAttrsRaw)) {
|
||||
$allAttrsRaw = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Group by variant and collect unique IDs for batch loading
|
||||
$attrsByVariant = [];
|
||||
$allAttrIds = [];
|
||||
$allValueIds = [];
|
||||
foreach ($allAttrsRaw as $a) {
|
||||
$pid = (int)$a['product_id'];
|
||||
$aId = (int)$a['attribute_id'];
|
||||
$vId = (int)$a['value_id'];
|
||||
$attrsByVariant[$pid][] = ['attribute_id' => $aId, 'value_id' => $vId];
|
||||
$allAttrIds[] = $aId;
|
||||
$allValueIds[] = $vId;
|
||||
}
|
||||
|
||||
$attrNamesMap = $this->batchLoadAttributeNames($allAttrIds);
|
||||
$valueNamesMap = $this->batchLoadValueNames($allValueIds);
|
||||
|
||||
$variants = [];
|
||||
foreach ($rows as $row) {
|
||||
$variantId = (int)$row['id'];
|
||||
$variantAttrs = [];
|
||||
if (isset($attrsByVariant[$variantId])) {
|
||||
foreach ($attrsByVariant[$variantId] as $a) {
|
||||
$aId = $a['attribute_id'];
|
||||
$vId = $a['value_id'];
|
||||
$variantAttrs[] = [
|
||||
'attribute_id' => $aId,
|
||||
'attribute_names' => isset($attrNamesMap[$aId]) ? $attrNamesMap[$aId] : [],
|
||||
'value_id' => $vId,
|
||||
'value_names' => isset($valueNamesMap[$vId]) ? $valueNamesMap[$vId] : [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$variants[] = [
|
||||
'id' => $variantId,
|
||||
'permutation_hash' => $row['permutation_hash'],
|
||||
'sku' => $row['sku'],
|
||||
'ean' => $row['ean'],
|
||||
'price_brutto' => $row['price_brutto'] !== null ? (float)$row['price_brutto'] : null,
|
||||
'price_brutto_promo' => $row['price_brutto_promo'] !== null ? (float)$row['price_brutto_promo'] : null,
|
||||
'price_netto' => $row['price_netto'] !== null ? (float)$row['price_netto'] : null,
|
||||
'price_netto_promo' => $row['price_netto_promo'] !== null ? (float)$row['price_netto_promo'] : null,
|
||||
'quantity' => (int)$row['quantity'],
|
||||
'stock_0_buy' => (int)($row['stock_0_buy'] ?? 0),
|
||||
'weight' => $row['weight'] !== null ? (float)$row['weight'] : null,
|
||||
'status' => (int)$row['status'],
|
||||
'attributes' => $variantAttrs,
|
||||
];
|
||||
}
|
||||
|
||||
return $variants;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera pojedynczy wariant po ID dla REST API.
|
||||
*
|
||||
* @param int $variantId ID wariantu
|
||||
* @return array|null Dane wariantu lub null
|
||||
*/
|
||||
public function findVariantForApi(int $variantId): ?array
|
||||
{
|
||||
$row = $this->db->get('pp_shop_products', '*', ['id' => $variantId]);
|
||||
if (!$row || empty($row['parent_id'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$attrs = $this->db->select('pp_shop_products_attributes', ['attribute_id', 'value_id'], ['product_id' => $variantId]);
|
||||
$variantAttrs = [];
|
||||
if (is_array($attrs) && !empty($attrs)) {
|
||||
$attrIds = [];
|
||||
$valueIds = [];
|
||||
foreach ($attrs as $a) {
|
||||
$attrIds[] = (int)$a['attribute_id'];
|
||||
$valueIds[] = (int)$a['value_id'];
|
||||
}
|
||||
$attrNamesMap = $this->batchLoadAttributeNames($attrIds);
|
||||
$valueNamesMap = $this->batchLoadValueNames($valueIds);
|
||||
|
||||
foreach ($attrs as $a) {
|
||||
$aId = (int)$a['attribute_id'];
|
||||
$vId = (int)$a['value_id'];
|
||||
$variantAttrs[] = [
|
||||
'attribute_id' => $aId,
|
||||
'attribute_names' => isset($attrNamesMap[$aId]) ? $attrNamesMap[$aId] : [],
|
||||
'value_id' => $vId,
|
||||
'value_names' => isset($valueNamesMap[$vId]) ? $valueNamesMap[$vId] : [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (int)$row['id'],
|
||||
'parent_id' => (int)$row['parent_id'],
|
||||
'permutation_hash' => $row['permutation_hash'],
|
||||
'sku' => $row['sku'],
|
||||
'ean' => $row['ean'],
|
||||
'price_brutto' => $row['price_brutto'] !== null ? (float)$row['price_brutto'] : null,
|
||||
'price_brutto_promo' => $row['price_brutto_promo'] !== null ? (float)$row['price_brutto_promo'] : null,
|
||||
'price_netto' => $row['price_netto'] !== null ? (float)$row['price_netto'] : null,
|
||||
'price_netto_promo' => $row['price_netto_promo'] !== null ? (float)$row['price_netto_promo'] : null,
|
||||
'quantity' => (int)$row['quantity'],
|
||||
'stock_0_buy' => (int)($row['stock_0_buy'] ?? 0),
|
||||
'weight' => $row['weight'] !== null ? (float)$row['weight'] : null,
|
||||
'status' => (int)$row['status'],
|
||||
'attributes' => $variantAttrs,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tworzy nowy wariant (kombinację) produktu przez API.
|
||||
*
|
||||
* @param int $parentId ID produktu nadrzędnego
|
||||
* @param array $data Dane wariantu (attributes, sku, ean, price_brutto, etc.)
|
||||
* @return array|null ['id' => int, 'permutation_hash' => string] lub null przy błędzie
|
||||
*/
|
||||
public function createVariantForApi(int $parentId, array $data): ?array
|
||||
{
|
||||
$parent = $this->db->get('pp_shop_products', ['id', 'archive', 'parent_id', 'vat'], ['id' => $parentId]);
|
||||
if (!$parent) {
|
||||
return null;
|
||||
}
|
||||
if (!empty($parent['archive'])) {
|
||||
return null;
|
||||
}
|
||||
if (!empty($parent['parent_id'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$attributes = isset($data['attributes']) && is_array($data['attributes']) ? $data['attributes'] : [];
|
||||
if (empty($attributes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build permutation hash
|
||||
ksort($attributes);
|
||||
$hashParts = [];
|
||||
foreach ($attributes as $attrId => $valueId) {
|
||||
$hashParts[] = (int)$attrId . '-' . (int)$valueId;
|
||||
}
|
||||
$permutationHash = implode('|', $hashParts);
|
||||
|
||||
// Check duplicate
|
||||
$existing = $this->db->count('pp_shop_products', [
|
||||
'AND' => [
|
||||
'parent_id' => $parentId,
|
||||
'permutation_hash' => $permutationHash,
|
||||
],
|
||||
]);
|
||||
if ($existing > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$insertData = [
|
||||
'parent_id' => $parentId,
|
||||
'permutation_hash' => $permutationHash,
|
||||
'vat' => $parent['vat'] ?? 0,
|
||||
'sku' => isset($data['sku']) ? (string)$data['sku'] : null,
|
||||
'ean' => isset($data['ean']) ? (string)$data['ean'] : null,
|
||||
'price_brutto' => isset($data['price_brutto']) ? (float)$data['price_brutto'] : null,
|
||||
'price_netto' => isset($data['price_netto']) ? (float)$data['price_netto'] : null,
|
||||
'quantity' => isset($data['quantity']) ? (int)$data['quantity'] : 0,
|
||||
'stock_0_buy' => isset($data['stock_0_buy']) ? (int)$data['stock_0_buy'] : 0,
|
||||
'weight' => isset($data['weight']) ? (float)$data['weight'] : null,
|
||||
'status' => 1,
|
||||
];
|
||||
|
||||
$this->db->insert('pp_shop_products', $insertData);
|
||||
$variantId = (int)$this->db->id();
|
||||
if ($variantId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Insert attribute rows
|
||||
foreach ($attributes as $attrId => $valueId) {
|
||||
$this->db->insert('pp_shop_products_attributes', [
|
||||
'product_id' => $variantId,
|
||||
'attribute_id' => (int)$attrId,
|
||||
'value_id' => (int)$valueId,
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $variantId,
|
||||
'permutation_hash' => $permutationHash,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualizuje wariant produktu przez API.
|
||||
*
|
||||
* @param int $variantId ID wariantu
|
||||
* @param array $data Pola do aktualizacji
|
||||
* @return bool true jeśli sukces
|
||||
*/
|
||||
public function updateVariantForApi(int $variantId, array $data): bool
|
||||
{
|
||||
$variant = $this->db->get('pp_shop_products', ['id', 'parent_id'], ['id' => $variantId]);
|
||||
if (!$variant || empty($variant['parent_id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$casts = [
|
||||
'sku' => 'string',
|
||||
'ean' => 'string',
|
||||
'price_brutto' => 'float_or_null',
|
||||
'price_netto' => 'float_or_null',
|
||||
'price_brutto_promo' => 'float_or_null',
|
||||
'price_netto_promo' => 'float_or_null',
|
||||
'quantity' => 'int',
|
||||
'stock_0_buy' => 'int',
|
||||
'weight' => 'float_or_null',
|
||||
'status' => 'int',
|
||||
];
|
||||
|
||||
$updateData = [];
|
||||
foreach ($casts as $field => $type) {
|
||||
if (array_key_exists($field, $data)) {
|
||||
$value = $data[$field];
|
||||
if ($type === 'string') {
|
||||
$updateData[$field] = ($value !== null) ? (string)$value : '';
|
||||
} elseif ($type === 'int') {
|
||||
$updateData[$field] = (int)$value;
|
||||
} elseif ($type === 'float_or_null') {
|
||||
$updateData[$field] = ($value !== null && $value !== '') ? (float)$value : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($updateData)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->db->update('pp_shop_products', $updateData, ['id' => $variantId]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Usuwa wariant produktu przez API.
|
||||
*
|
||||
* @param int $variantId ID wariantu
|
||||
* @return bool true jeśli sukces
|
||||
*/
|
||||
public function deleteVariantForApi(int $variantId): bool
|
||||
{
|
||||
$variant = $this->db->get('pp_shop_products', ['id', 'parent_id'], ['id' => $variantId]);
|
||||
if (!$variant || empty($variant['parent_id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->db->delete('pp_shop_products_langs', ['product_id' => $variantId]);
|
||||
$this->db->delete('pp_shop_products_attributes', ['product_id' => $variantId]);
|
||||
$this->db->delete('pp_shop_products', ['id' => $variantId]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-loads attribute names for multiple attribute IDs.
|
||||
*
|
||||
* @param int[] $attrIds
|
||||
* @return array<int, array<string, string>> [attrId => [langId => name]]
|
||||
*/
|
||||
private function batchLoadAttributeNames(array $attrIds): array
|
||||
{
|
||||
if (empty($attrIds)) {
|
||||
return [];
|
||||
}
|
||||
$translations = $this->db->select(
|
||||
'pp_shop_attributes_langs',
|
||||
['attribute_id', 'lang_id', 'name'],
|
||||
['attribute_id' => array_values(array_unique($attrIds))]
|
||||
);
|
||||
$result = [];
|
||||
if (is_array($translations)) {
|
||||
foreach ($translations as $t) {
|
||||
$aId = (int)($t['attribute_id'] ?? 0);
|
||||
$langId = (string)($t['lang_id'] ?? '');
|
||||
if ($aId > 0 && $langId !== '') {
|
||||
$result[$aId][$langId] = (string)($t['name'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-loads value names for multiple value IDs.
|
||||
*
|
||||
* @param int[] $valueIds
|
||||
* @return array<int, array<string, string>> [valueId => [langId => name]]
|
||||
*/
|
||||
private function batchLoadValueNames(array $valueIds): array
|
||||
{
|
||||
if (empty($valueIds)) {
|
||||
return [];
|
||||
}
|
||||
$translations = $this->db->select(
|
||||
'pp_shop_attributes_values_langs',
|
||||
['value_id', 'lang_id', 'name'],
|
||||
['value_id' => array_values(array_unique($valueIds))]
|
||||
);
|
||||
$result = [];
|
||||
if (is_array($translations)) {
|
||||
foreach ($translations as $t) {
|
||||
$vId = (int)($t['value_id'] ?? 0);
|
||||
$langId = (string)($t['lang_id'] ?? '');
|
||||
if ($vId > 0 && $langId !== '') {
|
||||
$result[$vId][$langId] = (string)($t['name'] ?? '');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-loads attribute types for multiple attribute IDs.
|
||||
*
|
||||
* @param int[] $attrIds
|
||||
* @return array<int, int> [attrId => type]
|
||||
*/
|
||||
private function batchLoadAttributeTypes(array $attrIds): array
|
||||
{
|
||||
if (empty($attrIds)) {
|
||||
return [];
|
||||
}
|
||||
$rows = $this->db->select(
|
||||
'pp_shop_attributes',
|
||||
['id', 'type'],
|
||||
['id' => array_values(array_unique($attrIds))]
|
||||
);
|
||||
$result = [];
|
||||
if (is_array($rows)) {
|
||||
foreach ($rows as $row) {
|
||||
$result[(int)$row['id']] = (int)($row['type'] ?? 0);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user