pdo = $pdo; $this->assetsDir = $assetsDir ?? __DIR__ . '/../assets/brand'; $this->assetsUrlBase = $assetsUrlBase ?? '/assets/brand'; if (!is_dir($this->assetsDir)) @mkdir($this->assetsDir, 0775, true); } public function load(): array { $defaults = [ 'site_name' => 'GeoLite', 'primary_color' => '#667eea', 'footer_text' => '© ' . date('Y') . ' GeoLite', 'logo_url' => null, 'hero_image' => null, ]; $db = $this->pdo->query("SELECT site_name, primary_color, footer_text, logo_url, hero_image FROM app_settings WHERE id = 1") ->fetch(PDO::FETCH_ASSOC) ?: []; // Optional file fallback if you keep settings.php $file = __DIR__ . '/../settings.php'; $fileArr = file_exists($file) ? include $file : []; return array_merge($defaults, array_filter($db), is_array($fileArr) ? array_filter($fileArr) : []); } /** Returns [settings, errors] */ public function save(array $post, array $files): array { $errors = []; $current = $this->load(); $site_name = trim($post['site_name'] ?? $current['site_name']); $primary_color = trim($post['primary_color'] ?? $current['primary_color']); $primary_color = $primary_color[0] === '#' ? $primary_color : '#'.$primary_color; $footer_text = trim($post['footer_text'] ?? $current['footer_text']); if ($site_name === '') $errors[] = 'Site name is required.'; if (!preg_match('/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/', $primary_color)) $errors[] = 'Primary color must be a valid hex color (e.g., #667eea).'; $logo_url = $current['logo_url']; $hero_url = $current['hero_image']; if (!empty($files['logo']['name'])) { $res = $this->processImageUpload($files['logo'], 'logo'); if (isset($res['error'])) $errors[] = $res['error']; else $logo_url = $res['url']; } if (!empty($files['hero_image']['name'])) { $res = $this->processImageUpload($files['hero_image'], 'hero'); if (isset($res['error'])) $errors[] = $res['error']; else $hero_url = $res['url']; } if (!empty($post['delete_logo']) && $logo_url) { $this->unlinkIfLocal($logo_url); $logo_url = null; } if (!empty($post['delete_hero']) && $hero_url) { $this->unlinkIfLocal($hero_url); $hero_url = null; } if ($errors) { return [[ 'site_name'=>$site_name,'primary_color'=>$primary_color,'footer_text'=>$footer_text, 'logo_url'=>$logo_url,'hero_image'=>$hero_url ], $errors]; } // PostgreSQL UPSERT $sql = "INSERT INTO app_settings (id, site_name, primary_color, footer_text, logo_url, hero_image, updated_at) VALUES (1, :site_name, :primary_color, :footer_text, :logo_url, :hero_image, CURRENT_TIMESTAMP) ON CONFLICT (id) DO UPDATE SET site_name = EXCLUDED.site_name, primary_color = EXCLUDED.primary_color, footer_text = EXCLUDED.footer_text, logo_url = EXCLUDED.logo_url, hero_image = EXCLUDED.hero_image, updated_at = CURRENT_TIMESTAMP"; $stmt = $this->pdo->prepare($sql); $stmt->execute([ ':site_name' => $site_name, ':primary_color' => $primary_color, ':footer_text' => $footer_text, ':logo_url' => $logo_url, ':hero_image' => $hero_url ]); return [$this->load(), []]; } private function processImageUpload(array $file, string $prefix): array { if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) return ['error' => 'Upload error for ' . $prefix]; $tmp = $file['tmp_name']; $finfo = @getimagesize($tmp); if (!$finfo) return ['error' => 'File for ' . $prefix . ' is not a valid image.']; $mime = $finfo['mime'] ?? ''; $ext = match ($mime) { 'image/png' => 'png', 'image/jpeg' => 'jpg', 'image/webp' => 'webp', 'image/svg+xml' => 'svg', default => null, }; if (!$ext) return ['error' => 'Unsupported image type for ' . $prefix . ' (png, jpg, webp, svg).']; $name = $prefix . '_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $ext; $dest = rtrim($this->assetsDir, '/\\') . DIRECTORY_SEPARATOR . $name; if (!move_uploaded_file($tmp, $dest)) return ['error' => 'Failed to store uploaded ' . $prefix . '.']; $url = rtrim($this->assetsUrlBase, '/') . '/' . $name; return ['url' => $url]; } private function unlinkIfLocal(string $url): void { $path = parse_url($url, PHP_URL_PATH); $candidate = rtrim($this->assetsDir, '/\\') . DIRECTORY_SEPARATOR . basename($path); if (strpos($candidate, rtrim($this->assetsDir, '/\\')) === 0 && file_exists($candidate)) { @unlink($candidate); } } }