db->prepare("SELECT id, slug, published_title AS title, published_html, published_css, seo_title, seo_description, published_at FROM cms_pages WHERE slug = :slug AND status = 'published' AND published_at IS NOT NULL LIMIT 1"); $query->execute(['slug' => $slug]); return $query->fetch() ?: null; } public function settings(): array { $settings = Settings::defaults(); foreach ($this->db->query('SELECT `key`, `value` FROM cms_settings') as $row) { $value = json_decode($row['value'], true); switch ($row['key']) { case 'site_name': if (is_string($value) && trim($value) !== '' && mb_strlen($value) <= 200) { $settings['name'] = $value; } break; case 'contact_email': if (is_string($value) && strlen($value) <= 254 && filter_var($value, FILTER_VALIDATE_EMAIL)) { $settings['contact_email'] = $value; } break; case 'contact_phone': if (is_string($value) && preg_match('/^[+0-9 ()-]{1,60}$/D', $value)) { $settings['contact_phone'] = $value; } break; case 'navigation': if (is_array($value) && array_is_list($value)) { $settings['navigation'] = array_values(array_filter(array_slice($value, 0, 50), static fn ($item) => is_array($item) && is_string($item['label'] ?? null) && trim($item['label']) !== '' && mb_strlen($item['label']) <= 200 && is_string($item['url'] ?? null) && Content::safeUrl($item['url'], true))); } break; } } return $settings; } public function insertInquiry(array $values): void { $now = gmdate('Y-m-d H:i:s'); $query = $this->db->prepare('INSERT INTO cms_inquiries (name, email, company, phone, message, source, consent_at, created_at, updated_at) VALUES (:name, :email, :company, :phone, :message, :source, :consent_at, :created_at, :updated_at)'); $query->execute(array_intersect_key($values, array_flip(['name', 'email', 'company', 'phone', 'message', 'source'])) + ['consent_at' => $now, 'created_at' => $now, 'updated_at' => $now]); } }