'POST', 'callback' => [$this, 'rest_create_preview'], 'permission_callback' => function () { return current_user_can('manage_options'); }, ]); } /** * 获取并校验模块列表(含预览/默认回退逻辑)。 */ private static function resolve_modules(): array { $modules = []; // 预览模式 $preview_id = get_query_var(self::PREVIEW_QUERY_VAR); if ($preview_id && current_user_can('manage_options')) { $cached = get_transient(self::PREVIEW_TRANSIENT_PREFIX . sanitize_key((string) $preview_id)); if (is_array($cached)) { $modules = $cached; } } if (empty($modules) && class_exists('AKSRT_Settings')) { $modules = AKSRT_Settings::get_homepage_modules(); } // 校验:过滤无效条目,若全部无效则清空以触发默认模板回退 if (!empty($modules)) { $allowed = self::allowed_types(); $valid = array_filter($modules, function ($row) use ($allowed) { return is_array($row) && !empty($row['type']) && in_array($row['type'], $allowed, true); }); $modules = empty($valid) ? [] : array_values($valid); } if (empty($modules)) { $modules = self::get_default_template('resource_standard'); } return $modules; } /** * 渲染入口(首页 CMS 形态调用)。 * * 优先级: * 1. ?aksrt_preview={id} 且管理员 → 读 transient 渲染 * 2. option homepage_modules * 3. 若空 → 渲染默认模板(resource_standard) */ public static function render(): void { $modules = self::resolve_modules(); echo '
'; foreach ($modules as $idx => $row) { if (!is_array($row) || empty($row['type'])) continue; $type = sanitize_key((string) $row['type']); $config = isset($row['config']) && is_array($row['config']) ? $row['config'] : []; set_query_var('aksrt_module_config', $config); set_query_var('aksrt_module_index', $idx); get_template_part('templates/parts/home-modules/module', $type); } // 清理 set_query_var('aksrt_module_config', null); set_query_var('aksrt_module_index', null); echo '
'; } /** * 全屏模块类型(不受侧栏约束)。 */ public static function fullbleed_types(): array { return ['hero']; } /** * 分区渲染:仅渲染指定区域的模块。 * * @param string $zone 'fullbleed' | 'body' */ public static function render_zone(string $zone = 'body'): void { $modules = self::resolve_modules(); $fullbleed = self::fullbleed_types(); $filtered = array_filter($modules, function ($row) use ($fullbleed, $zone) { $type = $row['type'] ?? ''; $is_fullbleed = in_array($type, $fullbleed, true); return $zone === 'fullbleed' ? $is_fullbleed : !$is_fullbleed; }); if (empty($filtered)) return; $wrapper_class = $zone === 'fullbleed' ? 'home-modules home-modules--fullbleed' : 'home-modules'; echo '
'; foreach ($filtered as $idx => $row) { if (!is_array($row) || empty($row['type'])) continue; $type = sanitize_key((string) $row['type']); $config = isset($row['config']) && is_array($row['config']) ? $row['config'] : []; set_query_var('aksrt_module_config', $config); set_query_var('aksrt_module_index', $idx); get_template_part('templates/parts/home-modules/module', $type); } set_query_var('aksrt_module_config', null); set_query_var('aksrt_module_index', null); echo '
'; } /** * 创建预览会话。 */ public function rest_create_preview(WP_REST_Request $request) { $params = $request->get_json_params(); if (!is_array($params)) $params = $request->get_params(); $modules = isset($params['modules']) && is_array($params['modules']) ? $params['modules'] : []; $clean = []; $allowed_types = self::allowed_types(); foreach ($modules as $row) { if (!is_array($row)) continue; $type = isset($row['type']) ? sanitize_key((string) $row['type']) : ''; if (!in_array($type, $allowed_types, true)) continue; $clean[] = [ 'type' => $type, 'config' => isset($row['config']) && is_array($row['config']) ? $row['config'] : [], ]; } $id = wp_generate_uuid4(); set_transient(self::PREVIEW_TRANSIENT_PREFIX . $id, $clean, 30 * MINUTE_IN_SECONDS); return new WP_REST_Response([ 'preview_id' => $id, 'preview_url' => add_query_arg(self::PREVIEW_QUERY_VAR, $id, home_url('/')), 'expires_in' => 30 * MINUTE_IN_SECONDS, ], 200); } public static function allowed_types(): array { return [ 'hero', 'banner', 'category', 'tabs', 'ranking', 'recent', 'tag_cloud', 'friend_links', 'html', 'hotspot', 'category_recommend_cards', // Phase 5: 运营型模块 'text_title', 'button_group', 'spacing', 'faq', 'data_grid', 'highlight_cards', 'partner_marquee', 'product_showcase', // Widget-区等价模块(补齐 zibll widget-user / widget-more 能力) 'user', 'more', ]; } /** * 默认模板(3 套预设)。 */ public static function get_default_template(string $name): array { switch ($name) { case 'news': return [ ['type' => 'banner', 'config' => []], ['type' => 'category', 'config' => ['count' => 8, 'columns' => 4]], ['type' => 'category', 'config' => ['count' => 8, 'columns' => 4]], ['type' => 'tag_cloud', 'config' => ['limit' => 30]], ]; case 'mixed': return [ ['type' => 'hero', 'config' => []], ['type' => 'recent', 'config' => ['post_type' => 'post', 'count' => 6, 'layout' => 'card']], ['type' => 'category', 'config' => ['count' => 6, 'columns' => 3]], ['type' => 'category', 'config' => ['count' => 6, 'columns' => 3]], ]; case 'resource_standard': default: return [ ['type' => 'hero', 'config' => []], ['type' => 'tabs', 'config' => ['count' => 8]], ['type' => 'recent', 'config' => ['post_type' => 'aksrt_resource', 'count' => 8, 'layout' => 'grid-cms']], ['type' => 'ranking', 'config' => ['metric' => 'downloads', 'count' => 6, 'post_type' => 'aksrt_resource']], ]; } } }