Files
wordpress-theme-aksrt/src/Core/HomeModules.php
T
kate522 8c5b1555c9 refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
2026-08-02 22:06:06 +08:00

223 lines
7.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Core;
defined('ABSPATH') || exit;
class HomeModules {
const PREVIEW_QUERY_VAR = 'aksrt_preview';
const PREVIEW_TRANSIENT_PREFIX = 'aksrt_home_preview_';
private static $instance = null;
private function __construct() {}
public static function get_instance(): self {
if (null === self::$instance) self::$instance = new self();
return self::$instance;
}
public function init(): void {
add_filter('query_vars', [$this, 'add_query_var']);
add_action('rest_api_init', [$this, 'register_routes']);
}
public function add_query_var(array $vars): array {
$vars[] = self::PREVIEW_QUERY_VAR;
return $vars;
}
public function register_routes(): void {
register_rest_route('aksrt/v1', '/preview/homepage', [
'methods' => '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\Settings')) {
$modules = \AKSRT\Settings\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 '<div class="home-modules">';
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 '</div>';
}
/**
* 全屏模块类型(不受侧栏约束)。
*/
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 '<div class="' . esc_attr($wrapper_class) . '">';
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 '</div>';
}
/**
* 创建预览会话。
*/
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' => ['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' => ['count' => 8, 'layout' => 'grid-cms']],
['type' => 'ranking', 'config' => ['metric' => 'downloads', 'count' => 6]],
];
}
}
}