parts already printed on this request */ private array $done = []; public function __construct(TemplateRenderer $renderer, ?callable $resolve = null) { $this->renderer = $renderer; $this->resolve = $resolve ?? [Resolver::class, 'for_location']; } public function register(): void { // After the main query and the themes' own hook setup, before the template is loaded. add_action('template_redirect', [$this, 'setup'], 99); } public function mode(): string { $template = function_exists('get_template') ? (string) get_template() : ''; if (function_exists('wp_is_block_theme') && wp_is_block_theme()) { $mode = 'block'; } else { $compat = ThemeCompat::detect($template); $mode = $compat !== '' ? $compat : 'classic'; } $filtered = apply_filters('zadex/theme/support', $mode, $template); return is_string($filtered) && $filtered !== '' ? $filtered : $mode; } public function setup(): void { if (EditorLoader::mode() === 'edit') { return; } $mode = $this->mode(); if ($mode === 'none') { return; } add_filter('body_class', [$this, 'body_class']); if ($mode === 'block') { add_filter('render_block', [$this, 'render_block'], 10, 2); return; } $compat = $mode !== 'classic' ? ThemeCompat::make($mode) : null; foreach (['header', 'footer'] as $part) { if (!$this->resolve($part)) { continue; } if ($compat && $compat->replace($part, fn() => $this->print_part($part))) { continue; } if ($part === 'header') { add_action('get_header', [$this, 'classic_header'], 10, 2); } else { add_action('get_footer', [$this, 'classic_footer'], 10, 2); } } } public function resolve(string $part): ?int { $id = ($this->resolve)($part); return is_int($id) && $id > 0 ? $id : null; } /** Prints a part once (compat hooks may fire more than once). */ public function print_part(string $part): void { if (!empty($this->done[$part]) || !($id = $this->resolve($part))) { return; } $this->done[$part] = true; Kses::begin(); echo wp_kses($this->renderer->render($id), Kses::allowed_html()); Kses::end(); } /** Block themes: swaps the header / footer template parts. */ public function render_block(string $content, array $block): string { if (($block['blockName'] ?? '') !== 'core/template-part') { return $content; } $area = self::part_area(is_array($block['attrs'] ?? null) ? $block['attrs'] : []); if ($area === '' || !($id = $this->resolve($area))) { return $content; } return Kses::restore(wp_kses($this->renderer->render($id), Kses::allowed_html())); } /** 'header' / 'footer' / '' for a template-part block's attributes. */ public static function part_area(array $attrs): string { $area = isset($attrs['area']) && is_string($attrs['area']) ? $attrs['area'] : ''; $slug = isset($attrs['slug']) && is_string($attrs['slug']) ? $attrs['slug'] : ''; if ($area === '' && $slug !== '' && class_exists('\WP_Theme_JSON_Resolver')) { $parts = \WP_Theme_JSON_Resolver::get_theme_data([], ['with_supports' => false])->get_template_parts(); if (isset($parts[$slug]['area']) && is_string($parts[$slug]['area'])) { $area = $parts[$slug]['area']; } } if ($area === '' && $slug !== '') { if ($slug === 'header' || str_contains($slug, 'header')) { $area = 'header'; } elseif ($slug === 'footer' || str_contains($slug, 'footer') || $slug === 'colophon') { $area = 'footer'; } } if ($area === '' && isset($attrs['tagName']) && is_string($attrs['tagName'])) { if ($attrs['tagName'] === 'header' || $attrs['tagName'] === 'footer') { $area = $attrs['tagName']; } } return in_array($area, ['header', 'footer'], true) ? $area : ''; } /** Classic fallback for get_header(). */ public function classic_header($name = null, $args = []): void { if (!empty($this->done['header']) || !($id = $this->resolve('header'))) { return; } $this->done['header'] = true; $zadex_template_html = $this->renderer->render($id); include __DIR__ . '/views/header.php'; self::swallow_theme_file('header', $name, is_array($args) ? $args : []); } /** Classic fallback for get_footer(). */ public function classic_footer($name = null, $args = []): void { if (!empty($this->done['footer']) || !($id = $this->resolve('footer'))) { return; } $this->done['footer'] = true; $zadex_template_html = $this->renderer->render($id); include __DIR__ . '/views/footer.php'; self::swallow_theme_file('footer', $name, is_array($args) ? $args : []); } /** * Loads the theme's header.php / footer.php once, discarding its output, so the require_once in * get_header() / get_footer() that runs right after this action loads nothing. */ public static function swallow_theme_file(string $part, $name, array $args = []): void { $templates = []; $name = (string) $name; if ($name !== '') { $templates[] = "{$part}-{$name}.php"; } $templates[] = "{$part}.php"; ob_start(); locate_template($templates, true, true, $args); ob_end_clean(); } public function body_class(array $classes): array { foreach (['header', 'footer'] as $part) { if ($this->resolve($part)) { $classes[] = 'zadex-has-' . $part; } } return $classes; } }