*/ private array $sections = []; /** * Register a section. Last writer wins on key collision so an extension * can intentionally override a built-in section if it ever needs to. */ public function register(Email_Report_Section_Interface $section): void { $this->sections[$section->key()] = $section; } /** * Drop a section by key. Free code shouldn't call this — it's here * for tests and extensions. */ public function unregister(string $key): void { unset($this->sections[$key]); } /** * @return array */ public function all(): array { return $this->sections; } /** * Resolve the ordered, enabled section list for a given config. Returned * in the order they should render. * * @param array $config Resolved Email Report config. * @return array */ public function resolve_for(array $config): array { $enabled = $config['sections_enabled'] ?? []; $resolved = []; foreach ($this->sections as $key => $section) { if (!in_array($key, $enabled, true)) { continue; } $resolved[$key] = $section; } /** * Filter the ordered section list for a single report. * * @since 1.9.0 * * @param array $resolved Ordered sections, key => section. * @param array $config Resolved config. */ $resolved = apply_filters('thinkrank_email_report_sections', $resolved, $config); // Re-validate after filter: drop anything that doesn't implement the contract. return array_values(array_filter( $resolved, static fn ($s) => $s instanceof Email_Report_Section_Interface )); } /** * Section catalog for the admin UI, in registry order. * * @return array */ public function describe_for_ui(): array { $out = []; foreach ($this->sections as $key => $section) { $out[] = [ 'key' => $key, 'label' => $section->label(), 'default_enabled' => $section->default_enabled(), ]; } return $out; } }