db = $db; } /** * @return array{items: array>, total: int} */ public function listForAdmin( array $filters, string $sortColumn = 'id', string $sortDir = 'DESC', int $page = 1, int $perPage = 15 ): array { $allowedSortColumns = [ 'id' => 'sp.id', 'name' => 'sp.name', 'status' => 'sp.status', 'condition_type' => 'sp.condition_type', 'date_from' => 'sp.date_from', 'date_to' => 'sp.date_to', ]; $sortSql = $allowedSortColumns[$sortColumn] ?? 'sp.id'; $sortDir = strtoupper(trim($sortDir)) === 'ASC' ? 'ASC' : 'DESC'; $page = max(1, $page); $perPage = min(self::MAX_PER_PAGE, max(1, $perPage)); $offset = ($page - 1) * $perPage; $where = ['1 = 1']; $params = []; $name = trim((string)($filters['name'] ?? '')); if ($name !== '') { if (strlen($name) > 255) { $name = substr($name, 0, 255); } $where[] = 'sp.name LIKE :name'; $params[':name'] = '%' . $name . '%'; } $status = trim((string)($filters['status'] ?? '')); if ($status === '0' || $status === '1') { $where[] = 'sp.status = :status'; $params[':status'] = (int)$status; } $whereSql = implode(' AND ', $where); $sqlCount = " SELECT COUNT(0) FROM pp_shop_promotion AS sp WHERE {$whereSql} "; $stmtCount = $this->db->query($sqlCount, $params); $countRows = $stmtCount ? $stmtCount->fetchAll() : []; $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0; $sql = " SELECT sp.id, sp.name, sp.status, sp.condition_type, sp.discount_type, sp.amount, sp.date_from, sp.date_to, sp.include_coupon, sp.include_product_promo, sp.min_product_count, sp.price_cheapest_product FROM pp_shop_promotion AS sp WHERE {$whereSql} ORDER BY {$sortSql} {$sortDir}, sp.id DESC LIMIT {$perPage} OFFSET {$offset} "; $stmt = $this->db->query($sql, $params); $items = $stmt ? $stmt->fetchAll() : []; return [ 'items' => is_array($items) ? $items : [], 'total' => $total, ]; } public function find(int $promotionId): array { if ($promotionId <= 0) { return $this->defaultPromotion(); } $promotion = $this->db->get('pp_shop_promotion', '*', ['id' => $promotionId]); if (!is_array($promotion)) { return $this->defaultPromotion(); } $promotion['id'] = (int)($promotion['id'] ?? 0); $promotion['status'] = $this->toSwitchValue($promotion['status'] ?? 0); $promotion['include_coupon'] = $this->toSwitchValue($promotion['include_coupon'] ?? 0); $promotion['include_product_promo'] = $this->toSwitchValue($promotion['include_product_promo'] ?? 0); $promotion['condition_type'] = (int)($promotion['condition_type'] ?? 1); $promotion['discount_type'] = (int)($promotion['discount_type'] ?? 1); $promotion['categories'] = $this->decodeIdList($promotion['categories'] ?? null); $promotion['condition_categories'] = $this->decodeIdList($promotion['condition_categories'] ?? null); return $promotion; } public function save(array $data): ?int { $promotionId = (int)($data['id'] ?? 0); $row = [ 'name' => trim((string)($data['name'] ?? '')), 'status' => $this->toSwitchValue($data['status'] ?? 0), 'condition_type' => (int)($data['condition_type'] ?? 1), 'discount_type' => (int)($data['discount_type'] ?? 1), 'amount' => $this->toNullableNumeric($data['amount'] ?? null), 'date_from' => $this->toNullableDate($data['date_from'] ?? null), 'date_to' => $this->toNullableDate($data['date_to'] ?? null), 'categories' => $this->encodeIdList($data['categories'] ?? null), 'condition_categories' => $this->encodeIdList($data['condition_categories'] ?? null), 'include_coupon' => $this->toSwitchValue($data['include_coupon'] ?? 0), 'include_product_promo' => $this->toSwitchValue($data['include_product_promo'] ?? 0), 'min_product_count' => $this->toNullableInt($data['min_product_count'] ?? null), 'price_cheapest_product' => $this->toNullableNumeric($data['price_cheapest_product'] ?? null), ]; if ($promotionId <= 0) { $this->db->insert('pp_shop_promotion', $row); $id = (int)$this->db->id(); if ($id <= 0) { return null; } $this->invalidateActivePromotionsCache(); \Shared\Helpers\Helpers::delete_dir('../temp/'); return $id; } $this->db->update('pp_shop_promotion', $row, ['id' => $promotionId]); $this->invalidateActivePromotionsCache(); \Shared\Helpers\Helpers::delete_dir('../temp/'); return $promotionId; } public function delete(int $promotionId): bool { if ($promotionId <= 0) { return false; } $deleted = $this->db->delete('pp_shop_promotion', ['id' => $promotionId]); $ok = (bool)$deleted; if ($ok) { $this->invalidateActivePromotionsCache(); } return $ok; } /** * @return array> */ public function categoriesTree($parentId = null): array { $rows = $this->db->select('pp_shop_categories', ['id'], [ 'parent_id' => $parentId, 'ORDER' => ['o' => 'ASC'], ]); if (!is_array($rows)) { return []; } $categories = []; foreach ($rows as $row) { $categoryId = (int)($row['id'] ?? 0); if ($categoryId <= 0) { continue; } $category = $this->db->get('pp_shop_categories', '*', ['id' => $categoryId]); if (!is_array($category)) { continue; } $translations = $this->db->select('pp_shop_categories_langs', '*', ['category_id' => $categoryId]); $category['languages'] = []; if (is_array($translations)) { foreach ($translations as $translation) { $langId = (string)($translation['lang_id'] ?? ''); if ($langId !== '') { $category['languages'][$langId] = $translation; } } } $category['title'] = $this->categoryTitle($category['languages']); $category['subcategories'] = $this->categoriesTree($categoryId); $categories[] = $category; } return $categories; } private function defaultPromotion(): array { return [ 'id' => 0, 'name' => '', 'status' => 1, 'condition_type' => 1, 'discount_type' => 1, 'amount' => null, 'date_from' => null, 'date_to' => null, 'categories' => [], 'condition_categories' => [], 'include_coupon' => 0, 'include_product_promo' => 0, 'min_product_count' => null, 'price_cheapest_product' => null, ]; } private function toSwitchValue($value): int { if (is_bool($value)) { return $value ? 1 : 0; } if (is_numeric($value)) { return ((int)$value) === 1 ? 1 : 0; } if (is_string($value)) { $normalized = strtolower(trim($value)); return in_array($normalized, ['1', 'on', 'true', 'yes'], true) ? 1 : 0; } return 0; } private function toNullableInt($value): ?int { if ($value === null) { return null; } if (is_string($value)) { $value = trim($value); if ($value === '') { return null; } } $intValue = (int)$value; return $intValue > 0 ? $intValue : null; } private function toNullableNumeric($value): ?string { if ($value === null) { return null; } $stringValue = trim((string)$value); if ($stringValue === '') { return null; } return str_replace(',', '.', $stringValue); } private function toNullableDate($value): ?string { $date = trim((string)$value); if ($date === '') { return null; } return $date; } private function encodeIdList($values): ?string { $ids = $this->normalizeIdList($values); if (empty($ids)) { return null; } return json_encode($ids); } /** * @return int[] */ private function decodeIdList($raw): array { if (is_array($raw)) { return $this->normalizeIdList($raw); } $text = trim((string)$raw); if ($text === '') { return []; } $decoded = json_decode($text, true); if (!is_array($decoded)) { return []; } return $this->normalizeIdList($decoded); } /** * @return int[] */ private function normalizeIdList($values): array { if ($values === null) { return []; } if (!is_array($values)) { $text = trim((string)$values); if ($text === '') { return []; } if (strpos($text, ',') !== false) { $values = explode(',', $text); } else { $values = [$text]; } } $ids = []; foreach ($values as $value) { $id = (int)$value; if ($id > 0) { $ids[$id] = $id; } } return array_values($ids); } private function categoryTitle(array $languages): string { $defaultLang = $this->defaultLanguageId(); if ($defaultLang !== '' && isset($languages[$defaultLang]['title'])) { $title = trim((string)$languages[$defaultLang]['title']); if ($title !== '') { return $title; } } foreach ($languages as $language) { $title = trim((string)($language['title'] ?? '')); if ($title !== '') { return $title; } } return ''; } private function defaultLanguageId(): string { if ($this->defaultLangId !== null) { return $this->defaultLangId; } $rows = $this->db->select('pp_langs', ['id', 'start', 'o'], [ 'status' => 1, 'ORDER' => ['start' => 'DESC', 'o' => 'ASC'], ]); if (is_array($rows) && !empty($rows)) { $this->defaultLangId = (string)($rows[0]['id'] ?? ''); } else { $this->defaultLangId = ''; } return $this->defaultLangId; } private function invalidateActivePromotionsCache(): void { if (!class_exists('\Shared\Cache\CacheHandler')) { return; } try { $cache = new \Shared\Cache\CacheHandler(); if (method_exists($cache, 'delete')) { $cache->delete('\shop\Promotion::get_active_promotions'); } } catch (\Throwable $e) { // Cache invalidation should not block save/delete. } } }