*/ function aksrt_widget_sidebar_area_ids(): array { return [ 'home_sidebar', 'sidebar-blog', 'sidebar-article', 'all_sidebar_top', 'all_sidebar_bottom', 'cat_sidebar', 'tag_sidebar', 'search_sidebar', ]; } } if (!function_exists('aksrt_widget_archive_area_ids')) { /** * Widget zones for archive pages (cat/tag/search). * * @return array */ function aksrt_widget_archive_area_ids(): array { return [ 'cat_top_fluid', 'cat_top_content', 'cat_bottom_content', 'cat_bottom_fluid', 'cat_sidebar', 'tag_top_fluid', 'tag_top_content', 'tag_bottom_content', 'tag_bottom_fluid', 'tag_sidebar', 'search_top_fluid', 'search_top_content', 'search_bottom_content', 'search_bottom_fluid', 'search_sidebar', ]; } } if (!function_exists('aksrt_widget_homepage_area_ids')) { /** * Homepage widget zones for homepage-only widgets. * * @return array */ function aksrt_widget_homepage_area_ids(): array { return [ 'home_top_fluid', 'home_top_content', 'home_bottom_content', 'home_bottom_fluid', ]; } } if (!function_exists('aksrt_widget_single_area_ids')) { /** * Single post/page widget zones. * * @return array */ function aksrt_widget_single_area_ids(): array { return [ 'single_top_fluid', 'single_top_content', 'single_bottom_content', 'single_bottom_fluid', ]; } } if (!function_exists('aksrt_widget_base_from_id')) { /** * Get widget base id from a stored widget instance id. */ function aksrt_widget_base_from_id(string $widget_id): string { return preg_replace('/-\d+$/', '', $widget_id) ?: $widget_id; } } if (!function_exists('aksrt_widget_allowed_sidebars')) { /** * Allowed sidebar ids for a widget base id. * * @return array */ function aksrt_widget_allowed_sidebars(string $widget_base_id): array { $sidebar_only = [ 'aksrt_about', 'aksrt_categories', 'aksrt_tag_cloud', 'aksrt_hot_posts', ]; $homepage_only = [ 'aksrt_category_recommend', ]; $hybrid = [ 'aksrt_tab_posts', 'aksrt_category_block', 'aksrt_category_tabs', ]; if (in_array($widget_base_id, $sidebar_only, true)) { return aksrt_widget_sidebar_area_ids(); } if (in_array($widget_base_id, $homepage_only, true)) { return aksrt_widget_homepage_area_ids(); } if (in_array($widget_base_id, $hybrid, true)) { return array_values(array_unique(array_merge( aksrt_widget_sidebar_area_ids(), aksrt_widget_homepage_area_ids() ))); } return []; } } if (!function_exists('aksrt_widget_is_allowed_in_sidebar')) { /** * Whether a widget base id can be assigned to the given sidebar id. */ function aksrt_widget_is_allowed_in_sidebar(string $widget_base_id, string $sidebar_id): bool { if ($widget_base_id === '' || $sidebar_id === '') { return true; } if ($sidebar_id === 'wp_inactive_widgets' || strpos($sidebar_id, 'orphaned_widgets') === 0) { return true; } $allowed = aksrt_widget_allowed_sidebars($widget_base_id); if (empty($allowed)) { return true; } return in_array($sidebar_id, $allowed, true); } } if (!function_exists('aksrt_widget_get_area_context')) { /** * Resolve widget area context from sidebar args. */ function aksrt_widget_get_area_context(array $args): string { $sidebar_id = isset($args['id']) ? (string) $args['id'] : ''; if (in_array($sidebar_id, aksrt_widget_sidebar_area_ids(), true)) { return 'sidebar'; } if (in_array($sidebar_id, aksrt_widget_homepage_area_ids(), true)) { return 'homepage'; } if (in_array($sidebar_id, aksrt_widget_archive_area_ids(), true)) { return 'archive'; } if (in_array($sidebar_id, aksrt_widget_single_area_ids(), true)) { return 'single'; } return 'other'; } } // ============================================================ // Shared Widget Form Helper — 公共表单字段与显示规则 // ============================================================ if (!function_exists('aksrt_widget_common_defaults')) { /** * Default values for shared widget instance fields. * * @return array */ function aksrt_widget_common_defaults(): array { return [ 'subtitle' => '', 'title_link' => '', 'show_type' => 'all', 'show_id_type' => '', 'show_ids' => '', 'sidebar_affix' => false, 'animation_in' => '', 'animation_repeat'=> false, ]; } } if (!function_exists('aksrt_widget_normalize_common')) { /** * Normalize common widget instance fields, filling defaults for legacy instances. * * @param array $instance * @return array */ function aksrt_widget_normalize_common(array $instance): array { $defaults = aksrt_widget_common_defaults(); $result = array_merge($defaults, $instance); $result['subtitle'] = sanitize_text_field($result['subtitle'] ?? ''); $result['title_link'] = esc_url_raw($result['title_link'] ?? ''); $result['show_type'] = in_array($result['show_type'] ?? '', ['all', 'pc', 'mobile'], true) ? $result['show_type'] : 'all'; $result['show_id_type'] = in_array($result['show_id_type'] ?? '', ['', 'include', 'exclude'], true) ? $result['show_id_type'] : ''; $result['show_ids'] = sanitize_text_field($result['show_ids'] ?? ''); $result['sidebar_affix'] = !empty($result['sidebar_affix']); $result['animation_in'] = sanitize_key($result['animation_in'] ?? ''); $result['animation_repeat']= !empty($result['animation_repeat']); return $result; } } if (!function_exists('aksrt_widget_render_common_form')) { /** * Render shared widget admin form fields. * * Call this from your widget's form() method after rendering custom fields. * * @param array $instance Current widget instance. */ function aksrt_widget_render_common_form(array $instance): void { $instance = aksrt_widget_normalize_common($instance); ?>





$new_instance * @param array $old_instance * @return array */ function aksrt_widget_update_common(array $new_instance, array $old_instance): array { $merged = array_merge($old_instance, $new_instance); return aksrt_widget_normalize_common($merged); } } if (!function_exists('aksrt_widget_render_common_before')) { /** * Render common wrapper attributes and classes before widget content. * * Outputs CSS classes for animation, affix, show_type, etc. * Call this at the start of widget() before rendering content. * * @param array $args Sidebar args. * @param array $instance Widget instance. * @return array Modified args with extra classes. */ function aksrt_widget_get_common_wrapper_classes(array $args, array $instance): array { $instance = aksrt_widget_normalize_common($instance); $classes = []; // Show type: PC or mobile if ($instance['show_type'] === 'pc') { $classes[] = 'aksrt-widget--pc-only'; } elseif ($instance['show_type'] === 'mobile') { $classes[] = 'aksrt-widget--mobile-only'; } // Sidebar affix if ($instance['sidebar_affix']) { $classes[] = 'aksrt-widget--affix'; } // Animation if ($instance['animation_in'] !== '') { $classes[] = 'aksrt-widget--animated'; $classes[] = 'aksrt-anim-' . $instance['animation_in']; if ($instance['animation_repeat']) { $classes[] = 'aksrt-anim-repeat'; } } return $classes; } } if (!function_exists('aksrt_widget_should_display_by_ids')) { /** * Check if the current post/page should display the widget based on show_id_type and show_ids. * * @param array $instance Widget instance. * @return bool */ function aksrt_widget_should_display_by_ids(array $instance): bool { $instance = aksrt_widget_normalize_common($instance); if ($instance['show_id_type'] === '' || $instance['show_ids'] === '') { return true; } $current_id = get_the_ID(); if (!$current_id) { return true; // Not on a singular page, allow display } $ids = array_filter(array_map('absint', explode(',', $instance['show_ids']))); if ($instance['show_id_type'] === 'include') { return in_array($current_id, $ids, true); } if ($instance['show_id_type'] === 'exclude') { return !in_array($current_id, $ids, true); } return true; } } if (!function_exists('aksrt_widget_render_location_help')) { /** * Render placement hint inside widget admin forms. */ function aksrt_widget_render_location_help(string $scope): void { $messages = [ 'sidebar' => __('仅限侧栏区域:首页侧栏、博客侧栏、文章侧栏、全站侧栏、分类/标签/搜索侧栏。', 'aksrt'), 'homepage' => __('仅限首页挂载区域:首页顶部/底部全宽、首页内容上方/下方。', 'aksrt'), 'hybrid' => __('支持侧栏、首页和归档页挂载区域。', 'aksrt'), 'archive' => __('仅限归档页挂载区域:分类页、标签页、搜索页的顶部/底部/侧栏。', 'aksrt'), ]; if (empty($messages[$scope])) { return; } printf( '

%s

', esc_html($messages[$scope]) ); } } if (!function_exists('aksrt_filter_widget_display_by_area')) { /** * Prevent widgets from rendering outside their allowed areas, * and apply per-widget display rules (show_type, show_id_type/show_ids). * * @param array|false $instance Widget instance. * @param WP_Widget $widget Widget object. * @param array $args Sidebar args. * @return array|false */ function aksrt_filter_widget_display_by_area($instance, WP_Widget $widget, array $args) { $sidebar_id = isset($args['id']) ? (string) $args['id'] : ''; $widget_base_id = isset($widget->id_base) ? (string) $widget->id_base : ''; if ($instance === false) { return false; } // Layer 1: Area legality check if (!aksrt_widget_is_allowed_in_sidebar($widget_base_id, $sidebar_id)) { return false; } // Layer 2: Display rule check (show_type for PC/mobile) if (is_array($instance) && function_exists('aksrt_widget_normalize_common')) { $normalized = aksrt_widget_normalize_common($instance); // Show type: PC / mobile filter if ($normalized['show_type'] === 'pc' && wp_is_mobile()) { return false; } if ($normalized['show_type'] === 'mobile' && !wp_is_mobile()) { return false; } // Show IDs: whitelist / blacklist filter if (!aksrt_widget_should_display_by_ids($normalized)) { return false; } } return $instance; } } add_filter('widget_display_callback', 'aksrt_filter_widget_display_by_area', 10, 3); if (!function_exists('aksrt_filter_widget_sidebar_assignments')) { /** * Move disallowed widget assignments back to the inactive widgets bucket. * * @param mixed $new_value New sidebars_widgets option value. * @param mixed $old_value Previous option value. * @return mixed */ function aksrt_filter_widget_sidebar_assignments($new_value, $old_value) { if (!is_array($new_value)) { return $new_value; } $removed = []; foreach ($new_value as $sidebar_id => $widget_ids) { if (!is_array($widget_ids) || $sidebar_id === 'wp_inactive_widgets') { continue; } $filtered = []; foreach ($widget_ids as $widget_id) { if (!is_string($widget_id) || $widget_id === '') { continue; } $widget_base_id = aksrt_widget_base_from_id($widget_id); if (aksrt_widget_is_allowed_in_sidebar($widget_base_id, (string) $sidebar_id)) { $filtered[] = $widget_id; } else { $removed[] = $widget_id; } } $new_value[$sidebar_id] = array_values($filtered); } if (empty($removed)) { return $new_value; } if (!isset($new_value['wp_inactive_widgets']) || !is_array($new_value['wp_inactive_widgets'])) { $new_value['wp_inactive_widgets'] = []; } $assigned = []; foreach ($new_value as $widget_ids) { if (!is_array($widget_ids)) { continue; } foreach ($widget_ids as $widget_id) { if (is_string($widget_id) && $widget_id !== '') { $assigned[$widget_id] = true; } } } foreach (array_values(array_unique($removed)) as $widget_id) { if (!isset($assigned[$widget_id]) && !in_array($widget_id, $new_value['wp_inactive_widgets'], true)) { $new_value['wp_inactive_widgets'][] = $widget_id; } } return $new_value; } } add_filter('pre_update_option_sidebars_widgets', 'aksrt_filter_widget_sidebar_assignments', 10, 2); /** * Ensure widget instance config exists. */ function aksrt_ensure_widget_instance(string $option_name, int $number, array $defaults): void { $instances = get_option($option_name, []); if (!is_array($instances)) { $instances = []; } if (!isset($instances['_multiwidget'])) { $instances['_multiwidget'] = 1; } if (empty($instances[$number]) || !is_array($instances[$number])) { $instances[$number] = $defaults; update_option($option_name, $instances); } } /** * Repair the sidebar-blog structure and seed default sidebar widgets on first install. */ function aksrt_maybe_repair_sidebar_blog_widgets(): void { $sidebars = get_option('sidebars_widgets', []); $seeded = (bool) get_option('aksrt_sidebar_blog_seeded', false); if (!is_array($sidebars)) { $sidebars = []; } $current = $sidebars['sidebar-blog'] ?? null; $needs_repair = false; $normalized = []; if (is_array($current)) { foreach ($current as $item) { if (is_array($item)) { $needs_repair = true; } if (is_string($item) && $item !== '') { $normalized[] = $item; } } } if ($needs_repair) { $sidebars['sidebar-blog'] = array_values($normalized); update_option('sidebars_widgets', $sidebars); update_option('aksrt_sidebar_blog_seeded', true); return; } if ($current !== null) { if (!$seeded) { update_option('aksrt_sidebar_blog_seeded', true); } return; } if ($seeded) { return; } aksrt_ensure_widget_instance('widget_aksrt_about', 2, [ 'title' => '', ]); aksrt_ensure_widget_instance('widget_aksrt_categories', 2, [ 'title' => __('分类目录', 'aksrt'), 'count' => 8, ]); aksrt_ensure_widget_instance('widget_aksrt_tag_cloud', 2, [ 'title' => __('标签云', 'aksrt'), 'count' => 15, ]); $sidebars['sidebar-blog'] = [ 'aksrt_about-2', 'aksrt_categories-2', 'aksrt_tag_cloud-2', ]; update_option('sidebars_widgets', $sidebars); update_option('aksrt_sidebar_blog_seeded', true); } /** * Register a block editor collection for AKSRT widgets. */ add_action('enqueue_block_editor_assets', function () { $screen = get_current_screen(); if (!$screen || $screen->id !== 'widgets') { return; } wp_add_inline_script('wp-blocks', ' document.addEventListener("DOMContentLoaded", function() { if (typeof wp !== "undefined" && wp.blocks && wp.blocks.registerBlockCollection) { wp.blocks.registerBlockCollection("aksrt", { title: "AKSRT 主题小工具", icon: undefined }); } }); '); }); /** * Assign AKSRT widgets to the AKSRT block category in the widgets editor. * * @param array $metadata Block metadata. * @return array */ add_filter('block_type_metadata', function ($metadata) { $aksrt_widgets = [ 'aksrt_about', 'aksrt_categories', 'aksrt_tag_cloud', ]; if (!empty($metadata['name']) && in_array($metadata['name'], $aksrt_widgets, true)) { $metadata['category'] = 'aksrt'; } return $metadata; }); /** * Show widget placement hints in the admin form. */ add_action('in_widget_form', function (WP_Widget $widget): void { $map = [ 'aksrt_about' => 'sidebar', 'aksrt_categories' => 'sidebar', 'aksrt_tag_cloud' => 'sidebar', 'aksrt_hot_posts' => 'sidebar', 'aksrt_tab_posts' => 'hybrid', 'aksrt_category_block' => 'hybrid', 'aksrt_category_tabs' => 'hybrid', 'aksrt_category_recommend' => 'homepage', ]; $scope = $map[$widget->id_base] ?? ''; if ($scope !== '') { aksrt_widget_render_location_help($scope); } }, 10, 1);