true, 'post_types' => ['post', 'page'], 'heading_levels' => [2, 3, 4], 'min_headings' => 4, 'position' => 'before_first_heading', 'title' => 'Table of Contents', 'show_title' => true, 'show_toggle' => true, 'initial_state' => 'show', 'show_hierarchy' => true, 'numbered_list' => true, 'exclude_patterns' => [], 'theme' => 'grey', 'lowercase_anchors' => true, 'hyphenate_anchors' => true, 'smooth_scroll' => true, ]; public function get(string $key, $default = null) { $config = get_option(self::OPTION_KEY, self::DEFAULTS); if (!isset($config[$key])) { return $default ?? self::DEFAULTS[$key] ?? null; } return $config[$key]; } public function getAll(): array { return array_merge(self::DEFAULTS, get_option(self::OPTION_KEY, [])); } public function update(array $data): bool { $current = $this->getAll(); $merged = array_merge($current, $data); $sanitized = $this->sanitize($merged); $existing = get_option(self::OPTION_KEY, []); if ($existing === $sanitized) { return true; } return update_option(self::OPTION_KEY, $sanitized); } public function isEnabled(): bool { return (bool) $this->get('enabled'); } public function getPostTypes(): array { return (array) $this->get('post_types'); } public function getHeadingLevels(): array { return (array) $this->get('heading_levels'); } public function getMinHeadings(): int { return (int) $this->get('min_headings'); } public function getPosition(): string { return (string) $this->get('position'); } public function getTitle(): string { return (string) $this->get('title'); } public function shouldShowTitle(): bool { return (bool) $this->get('show_title'); } public function shouldShowToggle(): bool { return (bool) $this->get('show_toggle'); } public function getInitialState(): string { return (string) $this->get('initial_state'); } public function shouldShowHierarchy(): bool { return (bool) $this->get('show_hierarchy'); } public function shouldNumberList(): bool { return (bool) $this->get('numbered_list'); } public function getExcludePatterns(): array { return (array) $this->get('exclude_patterns'); } public function getTheme(): string { return (string) $this->get('theme'); } public function shouldLowercaseAnchors(): bool { return (bool) $this->get('lowercase_anchors'); } public function shouldHyphenateAnchors(): bool { return (bool) $this->get('hyphenate_anchors'); } public function shouldSmoothScroll(): bool { return (bool) $this->get('smooth_scroll'); } public function reset(): bool { return update_option(self::OPTION_KEY, self::DEFAULTS); } private function sanitize(array $data): array { return [ 'enabled' => !empty($data['enabled']), 'post_types' => $this->sanitizePostTypes($data['post_types'] ?? []), 'heading_levels' => $this->sanitizeHeadingLevels($data['heading_levels'] ?? []), 'min_headings' => max(1, intval($data['min_headings'] ?? 4)), 'position' => $this->sanitizePosition($data['position'] ?? 'before_first_heading'), 'title' => sanitize_text_field($data['title'] ?? 'Table of Contents'), 'show_title' => !empty($data['show_title']), 'show_toggle' => !empty($data['show_toggle']), 'initial_state' => in_array($data['initial_state'] ?? 'show', ['show', 'hide']) ? $data['initial_state'] : 'show', 'show_hierarchy' => !empty($data['show_hierarchy']), 'numbered_list' => !empty($data['numbered_list']), 'exclude_patterns' => $this->sanitizeExcludePatterns($data['exclude_patterns'] ?? []), 'theme' => $this->sanitizeTheme($data['theme'] ?? 'grey'), 'lowercase_anchors' => !empty($data['lowercase_anchors']), 'hyphenate_anchors' => !empty($data['hyphenate_anchors']), 'smooth_scroll' => !empty($data['smooth_scroll']), ]; } private function sanitizePostTypes(array $types): array { $available_types = get_post_types(['public' => true], 'names'); return array_values(array_intersect($types, $available_types)); } private function sanitizeHeadingLevels(array $levels): array { $valid = array_filter($levels, fn($level) => is_numeric($level) && $level >= 1 && $level <= 6); return array_values(array_unique(array_map('intval', $valid))); } private function sanitizePosition(string $position): string { $valid = ['before_first_heading', 'after_first_heading', 'top', 'bottom']; return in_array($position, $valid, true) ? $position : 'before_first_heading'; } private function sanitizeExcludePatterns($patterns): array { if (is_string($patterns)) { $patterns = array_filter(array_map('trim', explode("\n", $patterns))); } if (!is_array($patterns)) { return []; } return array_values(array_map('sanitize_text_field', $patterns)); } private function sanitizeTheme(string $theme): string { $valid = ['grey', 'light-blue', 'white', 'black', 'transparent']; return in_array($theme, $valid, true) ? $theme : 'grey'; } }