settings = $settings; $this->frontend = $frontend; $this->renderer = $renderer; $this->halt = $halt ?? static function (): void { exit; }; $this->header = $header ?? static function (string $line): void { if (!headers_sent()) { header($line); } }; } public function register(): void { add_action('template_redirect', [$this, 'maybe_intercept'], 0); add_action('admin_bar_menu', [$this, 'admin_bar'], 100); add_action('admin_notices', [$this, 'admin_notice']); add_filter('zadex/editor/config', [$this, 'editor_config']); } /** Normalized `site` settings. */ public static function sanitize($input): array { $in = is_array($input) ? $input : []; $mode = isset($in['mode']) && is_string($in['mode']) && in_array($in['mode'], self::MODES, true) ? $in['mode'] : 'off'; $roles = []; foreach ((array) ($in['roles'] ?? []) as $role) { if (is_string($role) || is_int($role)) { $role = sanitize_key((string) $role); if ($role !== '' && $role !== 'administrator' && !in_array($role, $roles, true)) { $roles[] = $role; } } } return [ 'mode' => $mode, 'page' => isset($in['page']) && is_numeric($in['page']) ? max(0, (int) $in['page']) : 0, 'roles' => $roles, ]; } public function site(): array { $global = $this->settings->global(); return self::sanitize($global['site'] ?? []); } public function mode(): string { return $this->site()['mode']; } /** Whether the current user sees the real site while the mode is on. */ public function user_allowed(): bool { if (!is_user_logged_in()) { return false; } if (current_user_can('manage_options')) { return true; } $user = wp_get_current_user(); $roles = is_object($user) && isset($user->roles) ? (array) $user->roles : []; return (bool) array_intersect($roles, $this->site()['roles']); } /** Requests that are never replaced by the maintenance page. */ public static function is_bypassed_request(): bool { if (is_admin() || (function_exists('wp_doing_ajax') && wp_doing_ajax()) || (function_exists('wp_doing_cron') && wp_doing_cron()) || (defined('REST_REQUEST') && REST_REQUEST) || (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) || (defined('WP_CLI') && WP_CLI) ) { return true; } if (EditorLoader::mode() !== '') { return true; // the editor shell and its preview have their own permission checks } $pagenow = $GLOBALS['pagenow'] ?? ''; if (in_array($pagenow, ['wp-login.php', 'wp-register.php', 'wp-activate.php', 'wp-signup.php'], true)) { return true; } if ((function_exists('is_robots') && is_robots()) || (function_exists('is_favicon') && is_favicon())) { return true; } return false; } public function should_intercept(): bool { if ($this->mode() === 'off' || self::is_bypassed_request()) { return false; } return !$this->user_allowed(); } /** Status code, headers and robots policy for a mode. */ public static function response_for(string $mode): array { if ($mode === 'maintenance') { return ['status' => 503, 'headers' => ['Retry-After' => (string) self::RETRY_AFTER], 'noindex' => true]; } return ['status' => 200, 'headers' => [], 'noindex' => false]; } /** The page shown to visitors, or 0 when it is missing / trashed (the built-in page is used then). */ public function page_id(): int { $id = $this->site()['page']; if ($id <= 0) { return 0; } $post = get_post($id); if (!$post || in_array($post->post_status, ['trash', 'auto-draft', 'inherit'], true)) { return 0; } return (int) $post->ID; } public function maybe_intercept(): void { if (!$this->should_intercept()) { return; } $mode = $this->mode(); $this->send_headers($mode); $page = $this->page_id(); if ($page > 0) { $this->render_page($page); } else { $this->print_builtin_page($mode); } ($this->halt)(); } public function send_headers(string $mode): void { $response = self::response_for($mode); status_header($response['status']); nocache_headers(); foreach ($response['headers'] as $name => $value) { ($this->header)($name . ': ' . $value); } if ($response['noindex']) { ($this->header)('X-Robots-Tag: noindex, nofollow'); } if ($response['noindex']) { add_filter('wp_robots', static fn(array $r): array => array_merge($r, ['noindex' => true, 'nofollow' => true])); } } /** * Renders the chosen page in a blank "Zadex Canvas" document. The main query stays the visitor's request: the * page becomes the current post (PostData, for get_post() / template tags / the frontend bundle) and the * Renderer's dynamic-tag context, its CSS is queued for
, and the document title is the page's. */ private function render_page(int $page_id): void { $post = get_post($page_id); if (!$post instanceof \WP_Post || $this->frontend === null || $this->renderer === null) { $this->print_builtin_page($this->mode()); return; } PostData::set($post); $this->renderer->set_context(Context::for_post($page_id)); $renderer = $this->renderer; add_action('wp_enqueue_scripts', static fn() => Styles::document_tree($page_id, $renderer), 30); add_filter('document_title_parts', static fn(array $parts): array => ['title' => get_the_title($post), 'site' => (string) get_bloginfo('name')]); if (Document::is_builder($page_id) && !post_password_required($post)) { // Rendered before wp_head(): its CSS lands in , protected fragments are restored on output. $html = $this->frontend->render_post($page_id); } else { // A page not built with Zadex (or password protected): its content as WordPress prints it. $html = Kses::protect((string) apply_filters('the_content', get_the_content(null, false, $post))); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WordPress core filter. } Layout::open(true, Templates::body_classes(Templates::CANVAS)); echo '' . esc_html($this->notice_text($mode)) . '. ' . esc_html__('Visitors who are not logged in see the maintenance / coming soon page. Turn it off in the Zadex editor: Site settings > Site.', 'zadex') . '