feat(forum/shop): phase 6 dynamic page sidebars + moderator fix

Phase 6 — forum/shop widget alignment + page-sidebar matrix + block
editor coverage:

- SetupPart1: add register_page_sidebars() replicating Zibll
  zib_page_register_sidebar() — registers page_{sidebar,top_fluid,
  top_content,bottom_content,bottom_fluid}_{ID} for pages opted in
  via the `widgets_register_container` postmeta (serialized area-key
  list), cached via wp_cache. page.php already consumes these via
  dynamic_sidebar. Assertion script
  $CLAUDE_JOB_DIR/tmp/assert-page-sidebars.php passes (opt-in
  mapping + page.php consumption + 63 registered widgets).
- WidgetForumPlateModerator: fix two fatal `obs_start()` typos
  (lines 75, 153) → ob_start() (was "call to undefined function"
  on the apply-modal and main payload paths).

Forum 9 + shop 3 widget business fields already align with Zibll
bbs/shop widget definitions (count/showposts/number, orderby
dims, style, plate filter, paginate, current_plate, tabs group);
REST routes forum-topics/forum-terms/forum-boards (WidgetAjax),
forum/frontend/widgets/topics (Forum.php),
shop/frontend/widgets/products (ShopController.php) all carry
permission_callback. WidgetProductOneline registered (line 127).
Block editor covers all 63 widgets via core/legacy-widget (WP 5.8+).
CLAUDE.md is gitignored (no in-repo inc/classes/widgets residue).

Acceptance: php -l clean; audit exit 0; SetupPart1=502 lines,
WidgetForumPlateModerator=248 lines. Phase 6 done-when marked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 21:38:36 +08:00
co-authored by Claude Fable 5
parent cecc1a7b5e
commit d1b44cbcd5
3 changed files with 79 additions and 9 deletions
+70
View File
@@ -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 = '<div id="%1$s" class="zib-widget %2$s">';
$card_wrapper_before = '<div id="%1$s" class="zib-widget %2$s">';
$title_before = '<h3>';
$title_after = '</h3>';
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' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
}
}
}
}
@@ -72,7 +72,7 @@ class WidgetForumPlateModerator extends \WP_Widget {
if ($desc === '') {
$desc = __('欢迎有热情的用户申请成为版主,协助管理社区秩序。', 'aksrt');
}
obs_start();
ob_start();
echo '<button type="button" class="but hollow c-blue p2-10 em09" data-aksrt-mod-apply-open="' . esc_attr((string) $uid) . '">' . esc_html__('申请版主', 'aksrt') . '</button>';
echo '<div class="aksrt-mod-apply-modal" id="' . esc_attr((string) $uid) . '" hidden>';
echo '<div class="aksrt-mod-apply-modal__backdrop" data-aksrt-mod-apply-close></div>';
@@ -150,7 +150,7 @@ class WidgetForumPlateModerator extends \WP_Widget {
return ['html' => ''];
}
obs_start();
ob_start();
echo '<div class="zib-widget moderator-box aksrt-plate-mods style-' . esc_attr($style) . ' ajax-item" data-aksrt-plate-mods="1">';
if ($title_text !== '' || $apply_html !== '') {
echo '<div class="mb10 flex jsb ac aksrt-plate-mods__head">';