settings = $settings; } public function register(): void { // Custom post types are registered on init (priority 10): add the template filters after them. add_action('init', [$this, 'register_filters'], 99); add_filter('template_include', [$this, 'template_include'], 99); } public function register_filters(): void { foreach ($this->settings->post_types() as $type) { add_filter('theme_' . $type . '_templates', [$this, 'add_templates']); } } /** @return array template file => label */ public static function templates(): array { return [ self::CANVAS => __('Zadex Canvas', 'zadex'), self::FULL_WIDTH => __('Zadex Full Width', 'zadex'), ]; } /** Options for the editor's "Page layout" select (value '' = the theme's default layout). */ public static function options(): array { $out = [['value' => '', 'label' => __('Default (theme layout)', 'zadex')]]; foreach (self::templates() as $file => $label) { $out[] = ['value' => $file, 'label' => $label]; } return $out; } /** @param array $templates */ public function add_templates(array $templates): array { return array_merge($templates, self::templates()); } /** * Normalizes a template value: '' for the theme default (or anything unknown), otherwise one of the * Zadex template files. The `.php` suffix is optional on input. */ public static function sanitize($value): string { if (!is_string($value)) { return ''; } $value = trim($value); if ($value === '' || $value === 'default') { return ''; } if (!str_ends_with($value, '.php')) { $value .= '.php'; } return array_key_exists($value, self::templates()) ? $value : ''; } /** The Zadex template saved for a post; '' when it uses the theme's default or one of the theme's own templates. */ public static function get(int $post_id): string { return self::sanitize((string) get_post_meta($post_id, self::META, true)); } /** * Persists the editor's choice. '' (or 'default') only resets the meta when it currently holds a Zadex * template, so a theme template picked in the WordPress editor is never clobbered by a Zadex save. */ public static function save(int $post_id, $value): string { $clean = self::sanitize($value); if ($clean !== '') { update_post_meta($post_id, self::META, $clean); } elseif (self::get($post_id) !== '') { delete_post_meta($post_id, self::META); } return $clean; } /** Template for the current request: the editor's unsaved preview override, else the saved value. */ public static function current(int $post_id): string { // Unsaved page-template choice previewed inside the editor iframe: display only, limited to users who can // edit the post, and validated against the known templates by sanitize(). if (EditorLoader::is_preview() && isset($_GET[self::QUERY]) && current_user_can('edit_post', $post_id)) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return self::sanitize(sanitize_text_field(wp_unslash($_GET[self::QUERY]))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } return self::get($post_id); } /** template_include: serves the plugin template for singular views of enabled post types. */ public function template_include(string $template): string { if (!is_singular()) { return $template; } $post_id = (int) get_queried_object_id(); if (!$post_id || !in_array((string) get_post_type($post_id), $this->settings->post_types(), true)) { return $template; } $file = self::current($post_id); if ($file === '') { return $template; } $path = self::path($file); return $path !== null && is_file($path) ? $path : $template; } /** Absolute path of the PHP file that renders a Zadex template (null for anything else). */ public static function path(string $file): ?string { $map = [self::CANVAS => 'canvas.php', self::FULL_WIDTH => 'full-width.php']; if (!isset($map[$file])) { return null; } return (defined('ZADEX_PATH') ? ZADEX_PATH : dirname(__DIR__, 2) . '/') . 'includes/Frontend/templates/' . $map[$file]; } /** Body classes for a rendered template (used by the template files through the body_class filter). */ public static function body_classes(string $file): array { return ['zadex-template', 'zadex-template--' . ($file === self::CANVAS ? 'canvas' : 'full-width')]; } }