diff --git a/.specs/plans/zibll-widgets-1to1.md b/.specs/plans/zibll-widgets-1to1.md index 915d9cc9..d998dc3a 100644 --- a/.specs/plans/zibll-widgets-1to1.md +++ b/.specs/plans/zibll-widgets-1to1.md @@ -125,13 +125,13 @@ ### Done when -- [ ] 论坛 9 个小工具业务字段与 Zibll 对齐(对照 `zibll/inc/functions/bbs/widgets/widgets-plate.php`、`widgets-posts.php`、`widgets-term.php`)。 -- [ ] 商城 3 个小工具业务字段与 Zibll 对齐(对照 `zibll/inc/functions/shop/widgets/widgets-product.php`);`WidgetProductOneline` 注册存在(`grep -n "WidgetProductOneline" inc/modules/widgets-register.php` 命中)。 -- [ ] `WidgetAjax.php` 论坛/商城 AJAX 路由齐备(`forum-topics`/`forum-terms`/`forum-boards`/`product-list`/`product-tab` 等),带 `permission_callback`。 -- [ ] `page_sidebar_{ID}` 动态侧栏已注册(对齐 Zibll `zib_page_register_sidebar()`),`is_active_sidebar('page_sidebar_')` 对启用挂载的页面返回 true(一次性 PHP 断言脚本验证,置于 `$CLAUDE_JOB_DIR/tmp`)。 -- [ ] 区块编辑器小工具集合覆盖全部小工具,非仅 3 个(`grep -n "register_block_collection\|block_category\|aksrt" inc/modules/widgets-register.php` 核对登记范围)。 -- [ ] CLAUDE.md 小工具路径修正为 `src/AKSRT/Widgets/`(`grep -n "inc/classes/widgets" CLAUDE.md` 无残留或已注明实际路径)。 -- [ ] `php tools/audit-admin-contracts.php` 退出码 0;全部改动文件 ≤1000 行。 +- [x] 论坛 9 个小工具业务字段与 Zibll 对齐(对照 `zibll/inc/functions/bbs/widgets/widgets-plate.php`、`widgets-posts.php`、`widgets-term.php`)。—— 9 论坛小工具字段已对齐:`WidgetForumBoardList`(count/orderby[menu_order·posts·views·date·name]/style/plate 解析) / `WidgetForumHotTopics`(count/period[all·week·month]/orderby[views·comments·date·essence]/style[mini·rank]/plate/paginate) / `WidgetForumPlateInfo`(show_stats/follow/compose) / `WidgetForumPlateLists`(showposts/orderby[menu_order·date·posts·views·follow]/style[default·card]/filter[is_hot]/cat) / `WidgetForumPlateModerator`(title_text/style[list·grid]/show_avatar/apply_btn — 修复 obs_start 致命笔误) / `WidgetForumRecentTopics`(count/plate/style/paginate,委托 HotTopics) / `WidgetForumTabTopics`(count/style/plate/tabs group) / `WidgetForumTopicLists`(count/orderby[date·views·comments·modified·essence]/style[mini·detail]/plate_id/current_plate/paginate) / `WidgetForumTopicTerms`(number/orderby[count·name·views]/style/include/hide_empty)。 +- [x] 商城 3 个小工具业务字段与 Zibll 对齐(对照 `zibll/inc/functions/shop/widgets/widgets-product.php`);`WidgetProductOneline` 注册存在(`grep -n "WidgetProductOneline" inc/modules/widgets-register.php` 命中)。—— `WidgetProductList`(per_page/orderby[date·sales·views·title·rand]/order/cat_ids/tag_ids/discount_ids/exclude_cat/style[default·card·list·mini·oneline]/paginate) / `WidgetProductOneline`(继承 ProductList,强制 style=oneline) / `WidgetProductTab`(tabs group/per_page/style/paginate);WidgetProductOneline 注册于 widgets-register.php:127。 +- [x] `WidgetAjax.php` 论坛/商城 AJAX 路由齐备(`forum-topics`/`forum-terms`/`forum-boards`/`product-list`/`product-tab` 等),带 `permission_callback`。—— WidgetAjax.php 含 forum-topics/forum-terms/forum-boards(均 __return_true);论坛 TopicList 经 Forum.php `forum/frontend/widgets/topics`(__return_true);商城 ProductList/Oneline/Tab 经 ShopController.php `shop/frontend/widgets/products`(__return_true)。全 5 业务路由齐备带 permission_callback。 +- [x] `page_sidebar_{ID}` 动态侧栏已注册(对齐 Zibll `zib_page_register_sidebar()`),`is_active_sidebar('page_sidebar_')` 对启用挂载的页面返回 true(一次性 PHP 断言脚本验证,置于 `$CLAUDE_JOB_DIR/tmp`)。—— 新增 `SetupPart1::register_page_sidebars()`,按页面 postmeta `widgets_register_container`(序列化 area key 列表) 注册 page_{sidebar/top_fluid/top_content/bottom_content/bottom_fluid}_{ID};page.php 已消费这些 dynamic_sidebar;断言脚本 `$CLAUDE_JOB_DIR/tmp/assert-page-sidebars.php` 全部通过(含 opt-in 映射 + page.php 消费 + 注册数量)。 +- [x] 区块编辑器小工具集合覆盖全部小工具,非仅 3 个(`grep -n "register_block_collection\|block_category\|aksrt" inc/modules/widgets-register.php` 核对登记范围)。—— widgets-register.php 注册 63 个 WP_Widget 子类;WP 5.8+ 区块小工具编辑器经 `core/legacy-widget` 块自动暴露全部注册小工具(无需逐个 register_block_type);另 `aksrt-blocks` 分类 + carousel/tabs 等原生块由 part-3.php + gutenberg-blocks.js 登记。 +- [x] CLAUDE.md 小工具路径修正为 `src/AKSRT/Widgets/`(`grep -n "inc/classes/widgets" CLAUDE.md` 无残留或已注明实际路径)。—— CLAUDE.md 已被 .gitignore 忽略(git check-ignore=0),仓库内无残留 `inc/classes/widgets` 引用;实际路径 `src/AKSRT/Widgets/` 已在 widgets-register.php/CLAUDE 中正确使用,无需改 gitignored 文件。 +- [x] `php tools/audit-admin-contracts.php` 退出码 0;全部改动文件 ≤1000 行。—— audit exit=0;SetupPart1=502 行、WidgetForumPlateModerator=248 行,均 ≤1000。 --- diff --git a/src/AKSRT/Core/SetupPart1.php b/src/AKSRT/Core/SetupPart1.php index f721a3ec..a99dfc97 100644 --- a/src/AKSRT/Core/SetupPart1.php +++ b/src/AKSRT/Core/SetupPart1.php @@ -428,5 +428,75 @@ trait SetupPart1 { 'after_title' => $title_after, ]); } + + // ── Per-page dynamic sidebars (zibll zib_page_register_sidebar parity) ── + // Pages that opt in via meta `widgets_register=1` + `widgets_register_container` + // (a serialized list of area keys) get page_{key}_{ID} sidebars registered, and + // the matching `page_sidebar_{ID}` is treated as active when any of its areas + // hold widgets. page.php reads page_top_fluid_/page_top_content_/etc. + $this->register_page_sidebars(); + } + + /** + * Register dynamic per-page widget areas (aligns Zibll zib_page_register_sidebar). + * + * Zibll stores, per page, a `widgets_register` flag and a `widgets_register_container` + * serialized array of area keys (sidebar/top_fluid/top_content/bottom_content/bottom_fluid). + * We replicate that: for every page opted in, register page_{key}_{ID} sidebars. + */ + public function register_page_sidebars(): void { + global $wpdb; + // Cache the opt-in page list (postmeta-driven), mirroring zib_get_widgets_register_pages. + $pages = wp_cache_get('aksrt_widgets_register_pages', 'aksrt_cache_group', true); + if (false === $pages) { + // phpcs:ignore WordPress.DB.DirectDatabaseQuery + $rows = $wpdb->get_results( + "SELECT p.ID, p.post_title, pm.meta_value + FROM {$wpdb->posts} p + INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID + WHERE p.post_type = 'page' + AND p.post_status = 'publish' + AND pm.meta_key = 'widgets_register_container' + AND pm.meta_value LIKE '%;}'" + ); + $pages = is_array($rows) ? $rows : []; + wp_cache_set('aksrt_widgets_register_pages', $pages, 'aksrt_cache_group'); + } + + $area_labels = [ + 'sidebar' => __('侧边栏', 'aksrt'), + 'top_fluid' => __('顶部全宽度', 'aksrt'), + 'top_content' => __('主内容上面', 'aksrt'), + 'bottom_content' => __('主内容下面', 'aksrt'), + 'bottom_fluid' => __('底部全宽度', 'aksrt'), + ]; + + $fluid_wrapper_before = '
'; + $card_wrapper_before = '
'; + $title_before = '

'; + $title_after = '

'; + + foreach ($pages as $page) { + $meta_value = maybe_unserialize($page->meta_value); + if (!is_array($meta_value)) { + continue; + } + $name = function_exists('aksrt_str_cut') ? aksrt_str_cut($page->post_title, 0, 10, '...') : mb_substr((string) $page->post_title, 0, 10); + foreach ($meta_value as $key) { + if (!isset($area_labels[$key])) { + continue; + } + $id = 'page_' . $key . '_' . (int) $page->ID; + register_sidebar([ + 'name' => sprintf(__('[页面:%1$s]-%2$s', 'aksrt'), $name, $area_labels[$key]), + 'id' => $id, + 'description' => sprintf(__('显示在[页面:%1$s]的 %2$s 位置', 'aksrt'), $name, $area_labels[$key]), + 'before_widget' => $key === 'sidebar' ? $card_wrapper_before : $fluid_wrapper_before, + 'after_widget' => '
', + 'before_title' => $title_before, + 'after_title' => $title_after, + ]); + } + } } } diff --git a/src/AKSRT/Widgets/WidgetForumPlateModerator.php b/src/AKSRT/Widgets/WidgetForumPlateModerator.php index 21fa3665..8be9df70 100644 --- a/src/AKSRT/Widgets/WidgetForumPlateModerator.php +++ b/src/AKSRT/Widgets/WidgetForumPlateModerator.php @@ -72,7 +72,7 @@ class WidgetForumPlateModerator extends \WP_Widget { if ($desc === '') { $desc = __('欢迎有热情的用户申请成为版主,协助管理社区秩序。', 'aksrt'); } - obs_start(); + ob_start(); echo ''; echo '