feat(widgets): complete phase 3 atomic integration

This commit is contained in:
2026-08-17 18:21:47 +08:00
parent 7c3f4221fe
commit d5dfeb7102
89 changed files with 843 additions and 12886 deletions
-43
View File
@@ -112,49 +112,6 @@ class ImportExportController extends ControllerBase {
'permission_callback' => [$this, 'check_permission'],
]);
// ── Widget Template Management ──
// Export current widget configuration
register_rest_route(self::API_NAMESPACE, '/widgets/export', [
'methods' => 'GET',
'callback' => [$this, 'export_widgets'],
'permission_callback' => [$this, 'check_permission'],
]);
// Import widget configuration
register_rest_route(self::API_NAMESPACE, '/widgets/import', [
'methods' => 'POST',
'callback' => [$this, 'import_widgets'],
'permission_callback' => [$this, 'check_permission'],
]);
// List saved widget templates
register_rest_route(self::API_NAMESPACE, '/widgets/templates', [
'methods' => 'GET',
'callback' => [$this, 'list_widget_templates'],
'permission_callback' => [$this, 'check_permission'],
]);
// Save current layout as a named template
register_rest_route(self::API_NAMESPACE, '/widgets/templates', [
'methods' => 'POST',
'callback' => [$this, 'save_widget_template'],
'permission_callback' => [$this, 'check_permission'],
]);
// Apply a saved template
register_rest_route(self::API_NAMESPACE, '/widgets/templates/(?P<id>\d+)/apply', [
'methods' => 'POST',
'callback' => [$this, 'apply_widget_template'],
'permission_callback' => [$this, 'check_permission'],
]);
// Delete a saved template
register_rest_route(self::API_NAMESPACE, '/widgets/templates/(?P<id>\d+)', [
'methods' => 'DELETE',
'callback' => [$this, 'delete_widget_template'],
'permission_callback' => [$this, 'check_permission'],
]);
}
/* ------------------------------------------------------------------ */
-171
View File
@@ -144,177 +144,6 @@ trait ImportExportControllerPart1 {
]);
}
/* ------------------------------------------------------------------ */
/* Widget Template Handlers */
/* ------------------------------------------------------------------ */
/**
* GET /widgets/export — Export current widget configuration.
*/
public function export_widgets(\WP_REST_Request $request): \WP_REST_Response {
$sidebars_widgets = get_option('sidebars_widgets', []);
$widget_options = $this->collect_widget_options();
return $this->success([
'data' => [
'version' => AKSRT_VERSION ?? '3.4.0',
'exported' => gmdate('Y-m-d H:i:s'),
'sidebars_widgets' => $sidebars_widgets,
'widget_options' => $widget_options,
],
]);
}
/**
* POST /widgets/import — Import widget configuration.
*/
public function import_widgets(\WP_REST_Request $request): \WP_REST_Response {
$params = $request->get_json_params() ?? [];
$sidebars = $params['sidebars_widgets'] ?? null;
$options = $params['widget_options'] ?? null;
if (!$sidebars || !is_array($sidebars)) {
return $this->error('缺少 sidebars_widgets 数据');
}
// Validate sidebar IDs exist
$valid_sidebars = array_keys(get_registered_sidebars());
$valid_sidebars[] = 'wp_inactive_widgets';
$imported = 0;
foreach ($sidebars as $sidebar_id => $widget_ids) {
if (!in_array($sidebar_id, $valid_sidebars, true) && $sidebar_id !== 'array_version') {
continue;
}
$imported++;
}
// Import widget options if provided
if ($options && is_array($options)) {
foreach ($options as $option_key => $option_value) {
if (preg_match('/^widget_aksrt_/', $option_key)) {
update_option($option_key, $option_value);
}
}
}
// Import sidebar assignments
update_option('sidebars_widgets', $sidebars);
return $this->success(['message' => "已导入 {$imported} 个侧栏配置。"]);
}
/**
* GET /widgets/templates — List saved widget templates.
*/
public function list_widget_templates(\WP_REST_Request $request): \WP_REST_Response {
$templates = get_option('aksrt_widget_templates', []);
return $this->success(['data' => ['templates' => $templates]]);
}
/**
* POST /widgets/templates — Save current layout as a named template.
*/
public function save_widget_template(\WP_REST_Request $request): \WP_REST_Response {
$params = $request->get_json_params() ?? [];
$name = sanitize_text_field((string) ($params['name'] ?? ''));
if (!$name) {
return $this->error('请输入模板名称');
}
$templates = get_option('aksrt_widget_templates', []);
$template = [
'id' => count($templates) + 1,
'name' => $name,
'created_at' => gmdate('Y-m-d H:i:s'),
'sidebars_widgets' => get_option('sidebars_widgets', []),
'widget_options' => $this->collect_widget_options(),
];
$templates[] = $template;
update_option('aksrt_widget_templates', $templates);
return $this->success(['message' => '模板已保存', 'data' => $template]);
}
/**
* POST /widgets/templates/{id}/apply — Apply a saved template.
*/
public function apply_widget_template(\WP_REST_Request $request): \WP_REST_Response {
$id = (int) $request->get_param('id');
$templates = get_option('aksrt_widget_templates', []);
$template = null;
foreach ($templates as $t) {
if ((int) ($t['id'] ?? 0) === $id) {
$template = $t;
break;
}
}
if (!$template) {
return $this->error('模板不存在', 404);
}
// Apply widget options
if (!empty($template['widget_options']) && is_array($template['widget_options'])) {
foreach ($template['widget_options'] as $key => $value) {
update_option($key, $value);
}
}
// Apply sidebar assignments
if (!empty($template['sidebars_widgets'])) {
update_option('sidebars_widgets', $template['sidebars_widgets']);
}
return $this->success(['message' => '模板已应用']);
}
/**
* DELETE /widgets/templates/{id} — Delete a saved template.
*/
public function delete_widget_template(\WP_REST_Request $request): \WP_REST_Response {
$id = (int) $request->get_param('id');
$templates = get_option('aksrt_widget_templates', []);
$found = false;
$filtered = [];
foreach ($templates as $t) {
if ((int) ($t['id'] ?? 0) === $id) {
$found = true;
continue;
}
$filtered[] = $t;
}
if (!$found) {
return $this->error('模板不存在', 404);
}
update_option('aksrt_widget_templates', $filtered);
return $this->success(['message' => '模板已删除']);
}
/**
* Collect all widget options from the database.
*/
private function collect_widget_options(): array {
global $wpdb;
$rows = $wpdb->get_results(
"SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE 'widget_aksrt_%' ORDER BY option_name",
ARRAY_A
);
$options = [];
if ($rows) {
foreach ($rows as $row) {
$value = maybe_unserialize($row['option_value']);
$options[$row['option_name']] = $value;
}
}
return $options;
}
private function persist_setting_backup(array $backup): void {
$backups = get_option('aksrt_settings_backups', []);
+5 -6
View File
@@ -760,12 +760,11 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
}
// 单篇资源专属样式
if ($is_single_resource) {
$enqueue_module('single-post');
$enqueue_module('resource-meta');
$enqueue_module('comments');
$enqueue_module('widgets');
}
if ($is_single_resource) {
$enqueue_module('single-post');
$enqueue_module('resource-meta');
$enqueue_module('comments');
}
// 资源归档专属样式
if ($is_resource_archive) {
+2 -13
View File
@@ -71,13 +71,13 @@ trait ScriptsPart1c {
'aksrt-swiper',
$base_uri . '/assets/css/frontend/swiper-bundle.min.css',
[],
'11.0'
'11.2.10'
);
wp_enqueue_script(
'aksrt-swiper',
$base_uri . '/assets/js/swiper-bundle.min.js',
[],
'11.0',
'11.2.10',
true
);
}
@@ -299,17 +299,6 @@ trait ScriptsPart1c {
);
}
$widget_set_js = get_template_directory() . '/assets/js/widget-set.js';
if (file_exists($widget_set_js)) {
wp_enqueue_script(
'aksrt-widget-set',
$base_uri . '/assets/js/widget-set.js',
['aksrt-script', 'aksrt-qrcode'],
(string) filemtime($widget_set_js),
true
);
}
// ===== 全局 UI 增强(Toast、骨架屏、悬停动效、灯箱、滚动感知)=====
$enhancements_css = get_template_directory() . '/assets/css/frontend/enhancements.css';
if (file_exists($enhancements_css)) {
-2
View File
@@ -8,7 +8,6 @@ namespace AKSRT\Core;
defined('ABSPATH') || exit;
class Setup {
use SetupPart1;
use SetupPart2;
@@ -26,7 +25,6 @@ class Setup {
public function init(): void {
add_action('after_setup_theme', [$this, 'load_textdomain'], 1);
add_action('after_setup_theme', [$this, 'setup_theme']);
add_action('widgets_init', [$this, 'register_sidebars']);
add_filter('dynamic_sidebar_params', [$this, 'filter_sidebar_widget_params']);
add_action('init', [$this, 'register_nav_menus'], 0);
add_action('init', [$this, 'register_post_types'], 1);
-424
View File
@@ -1,424 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Core;
defined('ABSPATH') || exit;
trait SetupPart1 {
public function register_sidebars(): void {
$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>';
// ===== 全站 / 首页挂载位(对齐 Zibll 首页骨架) =====
register_sidebar([
'id' => 'all_top_fluid',
'name' => __('全站 - 顶部全宽', 'aksrt'),
'description' => __('显示在所有页面内容区上方的全宽挂载位。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'all_bottom_fluid',
'name' => __('全站 - 底部全宽', 'aksrt'),
'description' => __('显示在所有页面页脚上方的全宽挂载位。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'all_sidebar_top',
'name' => __('全站 - 侧栏顶部', 'aksrt'),
'description' => __('显示在全站侧栏顶部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'all_sidebar_bottom',
'name' => __('全站 - 侧栏底部', 'aksrt'),
'description' => __('显示在全站侧栏底部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'all_footer',
'name' => __('全站 - 页脚小工具', 'aksrt'),
'description' => __('显示在页脚内部的统一小工具区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'mobile_nav_fluid',
'name' => __('移动端 - 菜单底部', 'aksrt'),
'description' => __('显示在移动端抽屉菜单底部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_top_fluid',
'name' => __('首页 - 顶部全宽', 'aksrt'),
'description' => __('显示在首页主内容上方的全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_top_content',
'name' => __('首页 - 内容上方', 'aksrt'),
'description' => __('显示在首页主内容区顶部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_bottom_content',
'name' => __('首页 - 内容下方', 'aksrt'),
'description' => __('显示在首页主内容区底部。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_bottom_fluid',
'name' => __('首页 - 底部全宽', 'aksrt'),
'description' => __('显示在首页页脚上方的全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'home_sidebar',
'name' => __('首页 - 侧边栏', 'aksrt'),
'description' => __('显示在首页右侧栏。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// 文章详情页侧栏
register_sidebar([
'id' => 'sidebar-article',
'name' => __('文章页 - 侧边栏', 'aksrt'),
'description' => __('显示在文章详情页右侧。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// ===== 文章页 Widget 区域(模板zibll 多位置) =====
register_sidebar([
'id' => 'single_top_fluid',
'name' => __('文章页 - 顶部全宽', 'aksrt'),
'description' => __('文章页顶部全宽区域(在封面/面包屑之上)。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'single_top_content',
'name' => __('文章页 - 内容上方', 'aksrt'),
'description' => __('文章主体卡片上方(在内容列内)。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'single_bottom_content',
'name' => __('文章页 - 内容下方', 'aksrt'),
'description' => __('评论区下方(在内容列内)。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'single_bottom_fluid',
'name' => __('文章页 - 底部全宽', 'aksrt'),
'description' => __('文章页底部全宽区域(在页脚之上)。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// ===== 分类页 Widget 区域 =====
register_sidebar([
'id' => 'cat_top_fluid',
'name' => __('分类页 - 顶部全宽', 'aksrt'),
'description' => __('分类归档页顶部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'cat_top_content',
'name' => __('分类页 - 内容上方', 'aksrt'),
'description' => __('分类归档页内容区上方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'cat_bottom_content',
'name' => __('分类页 - 内容下方', 'aksrt'),
'description' => __('分类归档页内容区下方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'cat_bottom_fluid',
'name' => __('分类页 - 底部全宽', 'aksrt'),
'description' => __('分类归档页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'cat_sidebar',
'name' => __('分类页 - 侧栏', 'aksrt'),
'description' => __('分类归档页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// ===== 标签页 Widget 区域 =====
register_sidebar([
'id' => 'tag_top_fluid',
'name' => __('标签页 - 顶部全宽', 'aksrt'),
'description' => __('标签归档页顶部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'tag_top_content',
'name' => __('标签页 - 内容上方', 'aksrt'),
'description' => __('标签归档页内容区上方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'tag_bottom_content',
'name' => __('标签页 - 内容下方', 'aksrt'),
'description' => __('标签归档页内容区下方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'tag_bottom_fluid',
'name' => __('标签页 - 底部全宽', 'aksrt'),
'description' => __('标签归档页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'tag_sidebar',
'name' => __('标签页 - 侧栏', 'aksrt'),
'description' => __('标签归档页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
// ===== 搜索页 Widget 区域 =====
register_sidebar([
'id' => 'search_top_fluid',
'name' => __('搜索页 - 顶部全宽', 'aksrt'),
'description' => __('搜索结果页顶部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'search_top_content',
'name' => __('搜索页 - 内容上方', 'aksrt'),
'description' => __('搜索结果页内容区上方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'search_bottom_content',
'name' => __('搜索页 - 内容下方', 'aksrt'),
'description' => __('搜索结果页内容区下方。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'search_bottom_fluid',
'name' => __('搜索页 - 底部全宽', 'aksrt'),
'description' => __('搜索结果页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'search_sidebar',
'name' => __('搜索页 - 侧栏', 'aksrt'),
'description' => __('搜索结果页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'archive_bottom_fluid',
'name' => __('归档页 - 底部全宽', 'aksrt'),
'description' => __('分类/标签/归档页底部全宽区域。', 'aksrt'),
'before_widget' => $fluid_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'after_title' => $title_after,
]);
register_sidebar([
'id' => 'author-sidebar',
'name' => __('作者页 - 侧栏', 'aksrt'),
'description' => __('作者归档页侧栏区域。', 'aksrt'),
'before_widget' => $card_wrapper_before,
'after_widget' => '</div>',
'before_title' => $title_before,
'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,
]);
}
}
}
}
-351
View File
@@ -1,351 +0,0 @@
<?php
/**
* Public widget list REST endpoints.
*
* @package AKSRT
*/
namespace AKSRT\Core;
defined('ABSPATH') || exit;
class WidgetAjax {
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function init(): void {
add_action('rest_api_init', [$this, 'register_routes']);
}
public function register_routes(): void {
$routes = [
'/widgets/posts' => 'rest_main_posts',
'/widgets/tab-posts' => 'rest_tab_posts',
'/widgets/category-tabs' => 'rest_category_tabs',
'/widgets/category-block' => 'rest_category_block',
'/widgets/category-recommend' => 'rest_category_recommend',
'/widgets/hot-posts' => 'rest_hot_posts',
'/widgets/oneline-posts' => 'rest_oneline_posts',
'/widgets/recent-comments' => 'rest_recent_comments',
'/widgets/tag-cloud' => 'rest_tag_cloud',
'/widgets/categories' => 'rest_categories',
'/widgets/term-cards' => 'rest_term_cards',
'/widgets/term-aggregation' => 'rest_term_aggregation',
];
foreach ($routes as $path => $cb) {
register_rest_route('aksrt/v1', $path, [
'methods' => 'GET',
'callback' => [$this, $cb],
'permission_callback' => '__return_true',
]);
}
}
public function rest_main_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetMainPosts')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(30, (int) ($request->get_param('count') ?? 6))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($request->get_param('order') ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => max(0, (int) ($request->get_param('cat') ?? 0)),
'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)),
'pay_type' => sanitize_key((string) ($request->get_param('pay_type') ?? '')),
'style' => sanitize_key((string) ($request->get_param('style') ?? 'list')),
'show_thumb' => $request->get_param('show_thumb') === null ? 1 : (int) $request->get_param('show_thumb'),
'show_meta' => $request->get_param('show_meta') === null ? 1 : (int) $request->get_param('show_meta'),
'show_number' => (int) ($request->get_param('show_number') ?? 0),
'paginate' => in_array(sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')), ['none','ajax','number'], true) ? sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')) : 'ajax',
];
$page = max(1, (int) ($request->get_param('page') ?? $request->get_param('paged') ?? 1));
return $this->payload(\AKSRT\Widgets\WidgetMainPosts::query_payload($instance, $page), $page);
}
public function rest_tab_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\TabPostsWidget')) {
return $this->fail();
}
$default_count = max(1, min(20, (int) ($request->get_param('count') ?? 5)));
$style = sanitize_key((string) ($request->get_param('style') ?? 'mini'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'mini';
}
$paginate = sanitize_key((string) ($request->get_param('paginate') ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
$page = max(1, (int) ($request->get_param('page') ?? 1));
$tab = [
'title' => '',
'category_id' => max(0, (int) ($request->get_param('cat') ?? $request->get_param('category_id') ?? 0)),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($request->get_param('order') ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'count' => max(0, min(20, (int) ($request->get_param('tab_count') ?? 0))),
'enabled' => true,
'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)),
'pay_type' => sanitize_key((string) ($request->get_param('pay_type') ?? '')),];
$opts = [
'style' => $style,
'paginate' => $paginate,
'show_thumb' => $request->get_param('show_thumb') === null ? 1 : (int) $request->get_param('show_thumb'),
'show_meta' => $request->get_param('show_meta') === null ? 1 : (int) $request->get_param('show_meta'),
'show_number' => (int) ($request->get_param('show_number') ?? 0),
];
$payload = \AKSRT\Widgets\TabPostsWidget::render_tab_payload($tab, $default_count, $opts, $page);
$html = (string) ($payload['html'] ?? '');
if ($html === '') {
$html = function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
} else {
$html = '<div class="widget-tab-posts__list widget-tab-posts__list--' . esc_attr($style) . ' aksrt-main-posts style-' . esc_attr($style) . '">' . $html . '</div>';
}
return new \WP_REST_Response([
'success' => true,
'html' => $html,
'paginate_html' => (string) ($payload['paginate_html'] ?? ''),
'has_more' => !empty($payload['has_more']),
'page' => (int) ($payload['page'] ?? $page),
'max_pages' => (int) ($payload['max_pages'] ?? 1),
'total' => (int) ($payload['total'] ?? 0),
], 200);
}
public function rest_category_tabs(\WP_REST_Request $request) {
$cat = max(0, (int) ($request->get_param('cat') ?? 0));
$count = max(1, min(12, (int) ($request->get_param('count') ?? 6)));
$layout = sanitize_key((string) ($request->get_param('layout') ?? 'card'));
$columns = max(1, min(4, (int) ($request->get_param('columns') ?? 3)));
$context = sanitize_key((string) ($request->get_param('context') ?? 'homepage'));
if ($cat <= 0) {
return new \WP_REST_Response(['success' => true, 'html' => ''], 200);
}
$post_types = function_exists('aksrt_home_widget_post_types') ? aksrt_home_widget_post_types() : ['post'];
$query = new \WP_Query([
'post_type' => $post_types,
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'cat' => $cat,
]);
ob_start();
if (function_exists('aksrt_home_widget_render_post_grid')) {
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
} elseif ($query->have_posts()) {
if (function_exists('aksrt_render_mini_list')) {
echo '<div class="widget-tab-posts__list widget-tab-posts__list--mini">';
aksrt_render_mini_list($query, ['context' => 'widget', 'show_thumb' => true]);
echo '</div>';
} else {
echo '<ul class="widget-tab-posts__list">';
while ($query->have_posts()) {
$query->the_post();
echo '<li><a href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a></li>';
}
echo '</ul>';
}
} else {
echo function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
wp_reset_postdata();
$html = (string) ob_get_clean();
if ($html === '') {
$html = function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_hot_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\HotPostsWidget')) {
return $this->fail();
}
$paginate = sanitize_key((string) ($request->get_param('paginate') ?? 'ajax'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'ajax';
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))),
'order_by' => sanitize_key((string) ($request->get_param('order_by') ?? 'views')),
'days' => max(0, (int) ($request->get_param('days') ?? 30)),
'show_thumb' => $request->get_param('show_thumb') === null ? 1 : (int) $request->get_param('show_thumb'),
'target_blank'=> (int) ($request->get_param('target_blank') ?? 0),
'paginate' => $paginate,
];
$page = max(1, (int) ($request->get_param('page') ?? 1));
return $this->payload(\AKSRT\Widgets\HotPostsWidget::query_payload($instance, $page), $page);
}
public function rest_oneline_posts(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetOnelinePosts')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 8))),
'style' => sanitize_key((string) ($request->get_param('style') ?? 'card')),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($request->get_param('order') ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => max(0, (int) ($request->get_param('cat') ?? 0)),
'limit_day' => max(0, (int) ($request->get_param('limit_day') ?? 0)),
'pay_type' => sanitize_key((string) ($request->get_param('pay_type') ?? '')),
];
$html = \AKSRT\Widgets\WidgetOnelinePosts::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_recent_comments(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\RecentCommentsWidget')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 5))),
'show_avatar' => $request->get_param('show_avatar') === null ? 1 : (int) $request->get_param('show_avatar'),
'outpost' => sanitize_text_field((string) ($request->get_param('outpost') ?? '')),
'outer' => sanitize_text_field((string) ($request->get_param('outer') ?? '')),
];
$html = \AKSRT\Widgets\RecentCommentsWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_category_block(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\CategoryBlockWidget')) {
return $this->fail();
}
$instance = [
'category_id' => max(0, (int) ($request->get_param('cat') ?? $request->get_param('category_id') ?? 0)),
'count' => max(1, min(12, (int) ($request->get_param('count') ?? 6))),
'columns' => max(1, min(4, (int) ($request->get_param('columns') ?? 3))),
'layout' => sanitize_key((string) ($request->get_param('layout') ?? 'card')),
'paginate' => sanitize_key((string) ($request->get_param('paginate') ?? 'ajax')),
];
$context = sanitize_key((string) ($request->get_param('context') ?? 'homepage'));
$page = max(1, (int) ($request->get_param('page') ?? 1));
return $this->payload(\AKSRT\Widgets\CategoryBlockWidget::query_payload($instance, $page, $context), $page);
}
public function rest_category_recommend(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\CategoryRecommendWidget')) {
return $this->fail();
}
$raw_ids = $request->get_param('category_ids');
if (is_string($raw_ids)) {
$raw_ids = array_filter(array_map('intval', preg_split('/[\s,]+/', $raw_ids) ?: []));
}
$instance = [
'category_ids' => is_array($raw_ids) ? $raw_ids : [],
'posts_per_category' => max(1, min(8, (int) ($request->get_param('posts_per_category') ?? 5))),
'columns' => max(1, min(4, (int) ($request->get_param('columns') ?? 2))),
'show_views' => (int) ($request->get_param('show_views') ?? 0),
'show_description' => $request->get_param('show_description') === null ? 1 : (int) $request->get_param('show_description'),
'show_dates' => $request->get_param('show_dates') === null ? 1 : (int) $request->get_param('show_dates'),
'show_more' => $request->get_param('show_more') === null ? 1 : (int) $request->get_param('show_more'),
];
$html = \AKSRT\Widgets\CategoryRecommendWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_tag_cloud(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\TagCloudWidget')) {
return $this->fail();
}
$instance = [
'taxonomy' => sanitize_key((string) ($request->get_param('taxonomy') ?? 'post_tag')),
'count' => max(1, min(80, (int) ($request->get_param('count') ?? 15))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'color' => sanitize_html_class((string) ($request->get_param('color') ?? 'rand')),
'show_count' => (int) ($request->get_param('show_count') ?? 0),
'blank' => (int) ($request->get_param('blank') ?? 0),
'fixed_width' => (int) ($request->get_param('fixed_width') ?? 0),
'hide_empty' => (int) ($request->get_param('hide_empty') ?? 0),
];
$html = \AKSRT\Widgets\TagCloudWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_categories(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\CategoriesWidget')) {
return $this->fail();
}
$instance = [
'count' => max(1, min(50, (int) ($request->get_param('count') ?? 8))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'order' => sanitize_key((string) ($request->get_param('order') ?? 'DESC')),
'parent' => max(0, (int) ($request->get_param('parent') ?? 0)),
'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')),
'exclude' => sanitize_text_field((string) ($request->get_param('exclude') ?? '')),
'show_count' => $request->get_param('show_count') === null ? 1 : (int) $request->get_param('show_count'),
'hide_empty' => $request->get_param('hide_empty') === null ? 1 : (int) $request->get_param('hide_empty'),
'hierarchical' => (int) ($request->get_param('hierarchical') ?? 0),
];
$html = \AKSRT\Widgets\CategoriesWidget::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_term_cards(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetTermCards')) {
return $this->fail();
}
$instance = [
'taxonomy' => sanitize_key((string) ($request->get_param('taxonomy') ?? 'category')),
'number' => max(1, min(30, (int) ($request->get_param('number') ?? 8))),
'pc_row' => max(1, min(12, (int) ($request->get_param('pc_row') ?? $request->get_param('columns') ?? 2))),
'm_row' => max(1, min(12, (int) ($request->get_param('m_row') ?? 1))),
'columns' => max(1, min(12, (int) ($request->get_param('columns') ?? $request->get_param('pc_row') ?? 2))),
'type' => sanitize_key((string) ($request->get_param('type') ?? 'style-1')),
'height_scale' => max(20, min(300, (int) ($request->get_param('height_scale') ?? 60))),
'mask_opacity' => max(0, min(90, (int) ($request->get_param('mask_opacity') ?? 10))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'count')),
'include' => sanitize_text_field((string) ($request->get_param('include') ?? '')),
'hide_empty' => $request->get_param('hide_empty') === null ? 1 : (int) $request->get_param('hide_empty'),
'target_blank' => (int) ($request->get_param('target_blank') ?? 0),
];
$html = \AKSRT\Widgets\WidgetTermCards::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
public function rest_term_aggregation(\WP_REST_Request $request) {
if (!class_exists('\AKSRT\Widgets\WidgetTermAggregation')) {
return $this->fail();
}
$instance = [
'taxonomy' => sanitize_key((string) ($request->get_param('taxonomy') ?? 'category')),
'term_ids' => sanitize_text_field((string) ($request->get_param('term_ids') ?? '')),
'count' => max(1, min(20, (int) ($request->get_param('count') ?? 4))),
'columns' => max(1, min(3, (int) ($request->get_param('columns') ?? 1))),
'orderby' => sanitize_key((string) ($request->get_param('orderby') ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($request->get_param('order') ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'auto_terms' => max(1, min(4, (int) ($request->get_param('auto_terms') ?? 2))),
'target_blank' => (int) ($request->get_param('target_blank') ?? 0),
];
$html = \AKSRT\Widgets\WidgetTermAggregation::render_html($instance);
return new \WP_REST_Response(['success' => true, 'html' => $html], 200);
}
private function fail(): \WP_REST_Response {
return new \WP_REST_Response(['success' => false, 'code' => 'widget_unavailable', 'message' => __('小工具服务暂不可用。', 'aksrt'), 'data' => ['items' => [], 'page' => 1, 'max_pages' => 1, 'total' => 0]], 500);
}
private function payload(array $payload, int $page): \WP_REST_Response {
return new \WP_REST_Response([
'success' => true,
'code' => 'ok',
'html' => (string) ($payload['html'] ?? ''),
'paginate_html' => (string) ($payload['paginate_html'] ?? ''),
'has_more' => !empty($payload['has_more']),
'page' => (int) ($payload['page'] ?? $page),
'max_pages' => (int) ($payload['max_pages'] ?? 1),
'total' => (int) ($payload['total'] ?? 0),
], 200);
}
}
+4 -1
View File
@@ -19,18 +19,21 @@ final class AdminFormController {
public static function enqueue(): void {
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
if (!$screen || !in_array($screen->id, ['widgets', 'customize'], true)) {
$is_customizer = current_filter() === 'customize_controls_enqueue_scripts';
if (!$is_customizer && (!$screen || !in_array($screen->id, ['widgets', 'customize'], true))) {
return;
}
wp_enqueue_media();
wp_enqueue_style('wp-color-picker');
wp_enqueue_script('jquery-ui-sortable');
wp_enqueue_style('aksrt-widget-admin', AKSRT_URI . '/assets/widgets/admin.css', ['wp-color-picker'], AKSRT_VERSION);
wp_enqueue_style('aksrt-widget-preview', AKSRT_URI . '/assets/widgets/frontend.css', [], AKSRT_VERSION);
wp_enqueue_script('aksrt-widget-admin', AKSRT_URI . '/assets/widgets/admin.js', ['jquery', 'jquery-ui-sortable', 'wp-color-picker'], AKSRT_VERSION, true);
wp_localize_script('aksrt-widget-admin', 'aksrtWidgetAdmin', [
'ajaxUrl' => admin_url('admin-ajax.php'),
'action' => self::AJAX_ACTION,
'nonce' => wp_create_nonce(self::NONCE_ACTION),
'transferNonce' => wp_create_nonce('aksrt_widgets_transfer'),
'i18n' => [
'loading' => __('正在加载模块配置…', 'aksrt'),
'error' => __('加载失败,请重试。', 'aksrt'),
+17 -9
View File
@@ -9,6 +9,15 @@ final class AssetManager {
private static bool $registered = false;
private static bool $configured = false;
/** Enqueue before wp_head whenever at least one new widget is assigned. */
public static function enqueue_active(): void {
foreach (WidgetRegistry::all() as $definition) {
if (is_active_widget(false, false, $definition->id_base(), true)) {
self::enqueue_for($definition);
}
}
}
public static function enqueue_for(WidgetDefinition $definition): void {
self::register();
foreach ($definition->assets() as $asset) {
@@ -42,17 +51,17 @@ final class AssetManager {
wp_register_style('aksrt-widgets', $uri . '/assets/widgets/frontend.css', [], is_file($css) ? (string) filemtime($css) : null);
wp_register_script('aksrt-widgets', $uri . '/assets/widgets/frontend.js', [], is_file($js) ? (string) filemtime($js) : null, true);
if (!wp_style_is('aksrt-swiper', 'registered')) {
wp_register_style('aksrt-swiper', $uri . '/assets/css/frontend/swiper-bundle.min.css', [], '11.0');
wp_register_style('aksrt-swiper', $uri . '/assets/css/frontend/swiper-bundle.min.css', [], '11.2.10');
}
if (!wp_script_is('aksrt-swiper', 'registered')) {
wp_register_script('aksrt-swiper', $uri . '/assets/js/swiper-bundle.min.js', [], '11.0', true);
wp_register_script('aksrt-swiper', $uri . '/assets/js/swiper-bundle.min.js', [], '11.2.10', true);
}
wp_register_style('aksrt-widget-dplayer', 'https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.css', [], '1.27.1');
wp_register_script('aksrt-widget-hls', 'https://cdn.jsdelivr.net/npm/hls.js@1.5.18/dist/hls.min.js', [], '1.5.18', true);
wp_register_script('aksrt-widget-flv', 'https://cdn.jsdelivr.net/npm/flv.js@1.6.2/dist/flv.min.js', [], '1.6.2', true);
wp_register_script('aksrt-widget-dash', 'https://cdn.jsdelivr.net/npm/dashjs@4.7.4/dist/dash.all.min.js', [], '4.7.4', true);
wp_register_script('aksrt-widget-dplayer', 'https://cdn.jsdelivr.net/npm/dplayer@1.27.1/dist/DPlayer.min.js', ['aksrt-widget-hls', 'aksrt-widget-flv', 'aksrt-widget-dash'], '1.27.1', true);
wp_register_script('aksrt-widget-tilt', 'https://cdn.jsdelivr.net/npm/vanilla-tilt@1.8.1/dist/vanilla-tilt.min.js', [], '1.8.1', true);
$third_party = $uri . '/assets/widgets/third-party/';
wp_register_script('aksrt-widget-hls', $third_party . 'hls.min.js', [], 'reference-pinned', true);
wp_register_script('aksrt-widget-flv', $third_party . 'flv.min.js', [], 'reference-pinned', true);
wp_register_script('aksrt-widget-dash', $third_party . 'dash.all.min.js', [], 'reference-pinned', true);
wp_register_script('aksrt-widget-dplayer', $third_party . 'DPlayer.min.js', ['aksrt-widget-hls', 'aksrt-widget-flv', 'aksrt-widget-dash'], 'reference-pinned', true);
wp_register_script('aksrt-widget-tilt', $third_party . 'vanilla-tilt.min.js', [], 'reference-pinned', true);
self::$registered = true;
}
@@ -61,7 +70,6 @@ final class AssetManager {
wp_enqueue_style('aksrt-swiper');
wp_enqueue_script('aksrt-swiper');
} elseif ($asset === 'dplayer') {
wp_enqueue_style('aksrt-widget-dplayer');
wp_enqueue_script('aksrt-widget-dplayer');
} elseif (in_array($asset, ['hls', 'flv', 'dash'], true)) {
wp_enqueue_script('aksrt-widget-' . $asset);
+68
View File
@@ -0,0 +1,68 @@
<?php
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/**
* Single bootstrap for the schema-driven widget runtime.
*/
final class Runtime {
private static bool $registered = false;
public static function init(): void {
if (self::$registered) {
return;
}
self::$registered = true;
add_action('widgets_init', [self::class, 'unregister_core_widgets'], 1);
add_action('widgets_init', [WidgetAreas::class, 'register'], 5);
add_action('widgets_init', [self::class, 'register_widgets'], 10);
add_action('save_post_page', [WidgetAreas::class, 'flush_page_cache']);
add_action('rest_api_init', static function (): void {
(new RestController())->register_routes();
});
add_action('wp_enqueue_scripts', [AssetManager::class, 'enqueue_active'], 20);
add_action('in_widget_form', [self::class, 'render_placement_hint'], 20, 1);
AdminFormController::register_hooks();
TransferController::register_hooks();
}
public static function unregister_core_widgets(): void {
foreach ([
'WP_Widget_RSS',
'WP_Widget_Recent_Posts',
'WP_Widget_Recent_Comments',
'WP_Widget_Pages',
'WP_Widget_Search',
'WP_Widget_Calendar',
] as $class) {
unregister_widget($class);
}
}
public static function register_widgets(): void {
foreach (WidgetRegistry::all() as $definition) {
register_widget(new SchemaWidget($definition));
}
}
public static function render_placement_hint(\WP_Widget $widget): void {
if (!$widget instanceof SchemaWidget) {
return;
}
$definition = $widget->definition();
$messages = [
'wide' => __('宽版模块:建议放入全宽或主内容区域;放入侧栏时会显示尺寸警告。', 'aksrt'),
'sidebar' => __('侧栏模块:建议放入侧栏区域;放入全宽区域时会显示尺寸警告。', 'aksrt'),
'any' => __('自适应模块:可放入主内容、全宽或侧栏区域。', 'aksrt'),
];
printf(
'<p class="aksrt-widget-placement-hint" data-widget-size="%1$s"><span class="dashicons dashicons-info-outline" aria-hidden="true"></span>%2$s</p>',
esc_attr($definition->size()),
esc_html($messages[$definition->size()] ?? $messages['any'])
);
}
}
+3
View File
@@ -56,6 +56,9 @@ final class SchemaWidget extends \WP_Widget {
if (wp_doing_ajax() && isset($_REQUEST['action']) && $_REQUEST['action'] === AdminFormController::AJAX_ACTION) {
return false;
}
if (function_exists('is_customize_preview') && is_customize_preview()) {
return true;
}
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
return $screen && in_array($screen->id, ['widgets', 'customize'], true);
}
+153
View File
@@ -0,0 +1,153 @@
<?php
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/** Admin-only import, export, template application and preview. */
final class TransferController {
private const NONCE = 'aksrt_widgets_transfer';
public static function register_hooks(): void {
add_action('admin_notices', [self::class, 'render_panel']);
add_action('admin_post_aksrt_widgets_export', [self::class, 'handle_export']);
add_action('admin_post_aksrt_widgets_import', [self::class, 'handle_import']);
add_action('admin_post_aksrt_widgets_template', [self::class, 'handle_template']);
add_action('wp_ajax_aksrt_widgets_template_preview', [self::class, 'ajax_preview']);
}
public static function render_panel(): void {
$screen = function_exists('get_current_screen') ? get_current_screen() : null;
if (!$screen || $screen->id !== 'widgets' || !current_user_can('edit_theme_options')) {
return;
}
$sidebars = $GLOBALS['wp_registered_sidebars'] ?? [];
$templates = WidgetTransfer::templates();
$notice = isset($_GET['aksrt_widgets_notice']) ? sanitize_key(wp_unslash($_GET['aksrt_widgets_notice'])) : '';
if ($notice !== '') {
echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('小工具配置已更新。', 'aksrt') . '</p></div>';
}
?>
<div class="aksrt-widget-transfer postbox">
<div class="postbox-header"><h2><?php esc_html_e('AKSRT 小工具模板与导入导出', 'aksrt'); ?></h2></div>
<div class="inside aksrt-widget-transfer__grid">
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<h3><?php esc_html_e('导出', 'aksrt'); ?></h3>
<input type="hidden" name="action" value="aksrt_widgets_export">
<?php wp_nonce_field(self::NONCE); ?>
<?php self::sidebar_select($sidebars, 'sidebar', true); ?>
<?php submit_button(__('下载 aksrt.widgets.v1', 'aksrt'), 'secondary', 'submit', false); ?>
</form>
<form method="post" enctype="multipart/form-data" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<h3><?php esc_html_e('导入', 'aksrt'); ?></h3>
<input type="hidden" name="action" value="aksrt_widgets_import">
<?php wp_nonce_field(self::NONCE); ?>
<input type="file" name="widgets_file" accept="application/json,.json" required>
<?php self::mode_select(); ?>
<?php self::sidebar_select($sidebars, 'target_sidebar', true, __('沿用文件中的区域', 'aksrt')); ?>
<?php submit_button(__('导入配置', 'aksrt'), 'secondary', 'submit', false); ?>
</form>
<form method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
<h3><?php esc_html_e('内置模板', 'aksrt'); ?></h3>
<input type="hidden" name="action" value="aksrt_widgets_template">
<?php wp_nonce_field(self::NONCE); ?>
<select name="template" class="aksrt-widget-template-select">
<?php foreach ($templates as $key => $template) : ?>
<option value="<?php echo esc_attr($key); ?>"><?php echo esc_html((string) $template['label']); ?></option>
<?php endforeach; ?>
</select>
<?php self::mode_select(); ?>
<?php self::sidebar_select($sidebars, 'target_sidebar', true, __('使用模板默认区域', 'aksrt')); ?>
<div class="aksrt-widget-transfer__actions">
<button type="button" class="button aksrt-widget-template-preview"><?php esc_html_e('预览模块', 'aksrt'); ?></button>
<?php submit_button(__('应用模板', 'aksrt'), 'primary', 'submit', false); ?>
</div>
</form>
<div class="aksrt-widget-template-preview-output" hidden></div>
</div>
</div>
<?php
}
public static function handle_export(): void {
self::authorize();
$sidebar = isset($_POST['sidebar']) ? sanitize_key(wp_unslash($_POST['sidebar'])) : '';
$payload = WidgetTransfer::export($sidebar);
nocache_headers();
header('Content-Type: application/json; charset=utf-8');
header('Content-Disposition: attachment; filename="aksrt-widgets-' . gmdate('Ymd-His') . '.json"');
echo wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
public static function handle_import(): void {
self::authorize();
$file = $_FILES['widgets_file'] ?? null;
if (!is_array($file) || !empty($file['error']) || empty($file['tmp_name']) || (int) ($file['size'] ?? 0) > 2 * MB_IN_BYTES) {
wp_die(esc_html__('导入文件无效或超过 2MB。', 'aksrt'));
}
$payload = json_decode((string) file_get_contents((string) $file['tmp_name']), true);
if (!is_array($payload)) {
wp_die(esc_html__('导入文件不是有效 JSON。', 'aksrt'));
}
self::run_import($payload);
}
public static function handle_template(): void {
self::authorize();
$template = isset($_POST['template']) ? sanitize_key(wp_unslash($_POST['template'])) : '';
try {
$payload = WidgetTransfer::template_payload($template);
} catch (\InvalidArgumentException $error) {
wp_die(esc_html($error->getMessage()), '', ['response' => 400]);
}
self::run_import($payload);
}
public static function ajax_preview(): void {
check_ajax_referer(self::NONCE, 'nonce');
if (!current_user_can('edit_theme_options')) {
wp_send_json_error(['message' => __('权限不足。', 'aksrt')], 403);
}
$template = isset($_POST['template']) ? sanitize_key(wp_unslash($_POST['template'])) : '';
try {
wp_send_json_success(['html' => WidgetTransfer::preview($template)]);
} catch (\InvalidArgumentException $error) {
wp_send_json_error(['message' => $error->getMessage()], 400);
}
}
private static function authorize(): void {
if (!current_user_can('edit_theme_options')) {
wp_die(esc_html__('权限不足。', 'aksrt'), '', ['response' => 403]);
}
check_admin_referer(self::NONCE);
}
private static function run_import(array $payload): void {
$mode = isset($_POST['mode']) ? sanitize_key(wp_unslash($_POST['mode'])) : 'append';
$target = isset($_POST['target_sidebar']) ? sanitize_key(wp_unslash($_POST['target_sidebar'])) : '';
try {
WidgetTransfer::import($payload, $mode, $target);
} catch (\InvalidArgumentException $error) {
wp_die(esc_html($error->getMessage()), '', ['response' => 400]);
}
wp_safe_redirect(add_query_arg('aksrt_widgets_notice', 'updated', admin_url('widgets.php')));
exit;
}
private static function sidebar_select(array $sidebars, string $name, bool $allow_empty, string $empty_label = ''): void {
echo '<label><span class="screen-reader-text">' . esc_html__('小工具区域', 'aksrt') . '</span><select name="' . esc_attr($name) . '">';
if ($allow_empty) {
echo '<option value="">' . esc_html($empty_label !== '' ? $empty_label : __('全部区域', 'aksrt')) . '</option>';
}
foreach ($sidebars as $id => $sidebar) {
echo '<option value="' . esc_attr((string) $id) . '">' . esc_html((string) ($sidebar['name'] ?? $id)) . '</option>';
}
echo '</select></label>';
}
private static function mode_select(): void {
echo '<select name="mode"><option value="append">' . esc_html__('追加', 'aksrt') . '</option><option value="replace">' . esc_html__('替换区域', 'aksrt') . '</option></select>';
}
}
+146
View File
@@ -0,0 +1,146 @@
<?php
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/** Canonical 40 static areas plus five opt-in areas for each page. */
final class WidgetAreas {
private const PAGE_CACHE_KEY = 'aksrt_widget_pages_v1';
/** @return array<string, array{0:string,1:string,2:string}> */
public static function definitions(): array {
$areas = [
'all_top_fluid' => ['所有页面 - 顶部全宽', '显示在所有知识内容页面顶部。', 'fluid'],
'all_bottom_fluid' => ['所有页面 - 底部全宽', '显示在所有知识内容页面底部。', 'fluid'],
'all_sidebar_top' => ['所有页面 - 侧栏顶部', '显示在页面侧栏最上方。', 'sidebar'],
'all_sidebar_bottom' => ['所有页面 - 侧栏底部', '显示在页面侧栏最下方。', 'sidebar'],
];
$page_names = [
'home' => '首页',
'single' => '文章页',
'cat' => '分类页',
'tag' => '标签页',
'search' => '搜索页',
];
$positions = [
'top_fluid' => ['顶部全宽', 'fluid'],
'top_content' => ['主内容上面', 'content'],
'bottom_content' => ['主内容下面', 'content'],
'bottom_fluid' => ['底部全宽', 'fluid'],
'sidebar' => ['侧边栏', 'sidebar'],
];
foreach ($page_names as $prefix => $page_name) {
foreach ($positions as $suffix => [$position, $kind]) {
$areas[$prefix . '_' . $suffix] = [
$page_name . ' - ' . $position,
sprintf('显示在%s的%s位置。', $page_name, $position),
$kind,
];
}
}
foreach (['author' => '作者页', 'msg' => '消息中心'] as $prefix => $page_name) {
$areas[$prefix . '_top_fluid'] = [$page_name . ' - 顶部全宽', '显示在' . $page_name . '内容顶部。', 'fluid'];
$areas[$prefix . '_bottom_fluid'] = [$page_name . ' - 底部全宽', '显示在' . $page_name . '内容底部。', 'fluid'];
}
$areas['user_top_fluid'] = ['用户中心 - 顶部全宽', '显示在用户中心内容顶部。', 'fluid'];
$areas['user_top_content'] = ['用户中心 - 内容区上面', '显示在用户中心主内容上面。', 'content'];
$areas['user_bottom_fluid'] = ['用户中心 - 底部全宽', '显示在用户中心内容底部。', 'fluid'];
$areas['newposts_sidebar_top'] = ['前台投稿 - 侧栏顶部', '显示在前台投稿侧栏顶部,并在移动端保留。', 'sidebar'];
$areas['newposts_sidebar_bottom'] = ['前台投稿 - 侧栏底部', '显示在前台投稿侧栏底部,并在移动端保留。', 'sidebar'];
$areas['all_footer'] = ['页脚内部', '显示在站点页脚内部。', 'content'];
$areas['mobile_nav_fluid'] = ['移动菜单 - 底部', '显示在移动端抽屉菜单底部。', 'sidebar'];
return $areas;
}
/** @return string[] */
public static function static_ids(): array {
return array_keys(self::definitions());
}
public static function register(): void {
foreach (self::definitions() as $id => [$name, $description, $kind]) {
self::register_area($id, $name, $description, $kind);
}
self::register_page_areas();
}
public static function render(string $id, string $class = ''): bool {
if (!is_active_sidebar($id)) {
return false;
}
$class = trim('aksrt-widget-area aksrt-widget-area--' . sanitize_html_class($id) . ' ' . $class);
echo '<div class="' . esc_attr($class) . '" data-widget-area="' . esc_attr($id) . '">';
dynamic_sidebar($id);
echo '</div>';
return true;
}
public static function flush_page_cache(): void {
wp_cache_delete(self::PAGE_CACHE_KEY, 'aksrt');
}
private static function register_page_areas(): void {
$page_ids = wp_cache_get(self::PAGE_CACHE_KEY, 'aksrt');
if (!is_array($page_ids)) {
$page_ids = get_posts([
'post_type' => 'page',
'post_status' => 'publish',
'posts_per_page' => -1,
'fields' => 'ids',
'orderby' => 'date',
'order' => 'ASC',
'no_found_rows' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'meta_query' => [
[
'key' => 'widgets_register',
'value' => '1',
],
],
]);
$page_ids = array_map('absint', $page_ids);
wp_cache_set(self::PAGE_CACHE_KEY, $page_ids, 'aksrt');
}
$positions = [
'sidebar' => ['侧边栏', 'sidebar'],
'top_fluid' => ['顶部全宽', 'fluid'],
'top_content' => ['主内容上面', 'content'],
'bottom_content' => ['主内容下面', 'content'],
'bottom_fluid' => ['底部全宽', 'fluid'],
];
foreach ($page_ids as $page_id) {
$enabled = get_post_meta($page_id, 'widgets_register_container', true);
if (!is_array($enabled)) {
continue;
}
$title = wp_html_excerpt(get_the_title($page_id), 12, '…');
foreach (array_values(array_unique(array_map('sanitize_key', $enabled))) as $position) {
if (!isset($positions[$position])) {
continue;
}
[$label, $kind] = $positions[$position];
self::register_area(
'page_' . $position . '_' . $page_id,
sprintf('[页面:%1$s] - %2$s', $title, $label),
sprintf('显示在页面“%1$s”的%2$s位置。', $title, $label),
$kind
);
}
}
}
private static function register_area(string $id, string $name, string $description, string $kind): void {
register_sidebar([
'id' => $id,
'name' => __($name, 'aksrt'),
'description' => __($description, 'aksrt'),
'before_widget' => '<section id="%1$s" class="aksrt-widget-shell aksrt-widget-shell--' . esc_attr($kind) . ' %2$s">',
'after_widget' => '</section>',
'before_title' => '<h3 class="aksrt-widget-shell__title">',
'after_title' => '</h3>',
]);
}
}
+214
View File
@@ -0,0 +1,214 @@
<?php
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/** Import/export contract for schema widgets. */
final class WidgetTransfer {
public const FORMAT = 'aksrt.widgets.v1';
/** @return array<string, array<string,mixed>> */
public static function templates(): array {
return [
'article_home' => [
'label' => __('文章首页', 'aksrt'),
'description' => __('公告、幻灯片、多栏目文章与热榜侧栏。', 'aksrt'),
'sidebars' => [
'home_top_content' => [
['widget' => 'notice', 'settings' => ['title' => '站点公告']],
['widget' => 'slider', 'settings' => ['title' => '精选内容']],
['widget' => 'tab_posts', 'settings' => ['title' => '热门栏目']],
],
'home_sidebar' => [['widget' => 'hot_posts', 'settings' => ['title' => '热门文章']]],
],
],
'article_list' => [
'label' => __('文章列表', 'aksrt'),
'description' => __('标题、分类卡片与可分页文章列表。', 'aksrt'),
'sidebars' => [
'home_top_content' => [
['widget' => 'text_title', 'settings' => ['heading' => '最新知识内容', 'description' => '持续更新的课程、文章与资料']],
['widget' => 'term_cards', 'settings' => ['title' => '内容分类']],
['widget' => 'posts', 'settings' => ['title' => '最新发布', 'paginate' => 'number']],
],
],
],
'links_navigation' => [
'label' => __('链接导航', 'aksrt'),
'description' => __('搜索、分类入口与链接列表。', 'aksrt'),
'sidebars' => [
'home_top_content' => [
['widget' => 'icon_cover_cards', 'settings' => ['title' => '常用入口']],
['widget' => 'links', 'settings' => ['title' => '链接导航']],
],
'home_sidebar' => [['widget' => 'search', 'settings' => ['title' => '快速查找']]],
],
],
'article_sidebar' => [
'label' => __('文章侧栏', 'aksrt'),
'description' => __('目录、作者、付费授权与热榜文章。', 'aksrt'),
'sidebars' => [
'single_sidebar' => [
['widget' => 'posts_toc', 'settings' => ['title' => '文章目录']],
['widget' => 'author_card', 'settings' => ['title' => '作者']],
['widget' => 'post_pay', 'settings' => []],
['widget' => 'hot_posts', 'settings' => ['title' => '热门文章']],
],
],
],
'knowledge_landing' => [
'label' => __('知识付费落地页', 'aksrt'),
'description' => __('亮点、方案、评价与常见问题。', 'aksrt'),
'sidebars' => [
'home_top_content' => [
['widget' => 'feature_intro', 'settings' => ['title' => '系统化学习路径']],
['widget' => 'highlights', 'settings' => ['title' => '内容亮点']],
['widget' => 'pricing_plans', 'settings' => ['title' => '选择学习方案']],
['widget' => 'reviews', 'settings' => ['title' => '学员评价']],
['widget' => 'faq', 'settings' => ['title' => '常见问题']],
],
],
],
];
}
public static function template_payload(string $template): array {
$templates = self::templates();
if (!isset($templates[$template])) {
throw new \InvalidArgumentException(__('未知的小工具模板。', 'aksrt'));
}
return [
'format' => self::FORMAT,
'template' => $template,
'sidebars' => $templates[$template]['sidebars'],
];
}
public static function export(string $sidebar = ''): array {
$assignments = wp_get_sidebars_widgets();
$sidebars = [];
foreach ($assignments as $sidebar_id => $widget_ids) {
if ($sidebar !== '' && $sidebar_id !== $sidebar) {
continue;
}
if (!is_array($widget_ids)) {
continue;
}
$items = [];
foreach ($widget_ids as $widget_id) {
$item = self::export_widget((string) $widget_id);
if ($item !== null) {
$items[] = $item;
}
}
if ($items || $sidebar !== '') {
$sidebars[$sidebar_id] = $items;
}
}
return [
'format' => self::FORMAT,
'exported_at' => gmdate('c'),
'sidebars' => $sidebars,
];
}
/** @return array{sidebars:int,widgets:int} */
public static function import(array $payload, string $mode = 'append', string $target_sidebar = ''): array {
if (($payload['format'] ?? '') !== self::FORMAT || !isset($payload['sidebars']) || !is_array($payload['sidebars'])) {
throw new \InvalidArgumentException(__('仅接受 aksrt.widgets.v1 格式。', 'aksrt'));
}
$mode = $mode === 'replace' ? 'replace' : 'append';
$registered = array_keys($GLOBALS['wp_registered_sidebars'] ?? []);
$assignments = wp_get_sidebars_widgets();
$sources = array_slice($payload['sidebars'], 0, 100, true);
if ($target_sidebar !== '') {
$combined = [];
foreach ($sources as $items) {
if (is_array($items)) {
$combined = array_merge($combined, $items);
}
}
$sources = [$target_sidebar => $combined];
}
$changed = 0;
$created = 0;
foreach ($sources as $source_sidebar => $items) {
$sidebar_id = $target_sidebar !== '' ? sanitize_key($target_sidebar) : sanitize_key((string) $source_sidebar);
if (!in_array($sidebar_id, $registered, true) || !is_array($items)) {
continue;
}
$current = isset($assignments[$sidebar_id]) && is_array($assignments[$sidebar_id]) ? $assignments[$sidebar_id] : [];
if ($mode === 'replace') {
$current = [];
}
foreach (array_slice($items, 0, 100) as $item) {
if (!is_array($item)) {
continue;
}
$widget_id = self::create_widget($item);
if ($widget_id !== '') {
$current[] = $widget_id;
$created++;
}
}
$assignments[$sidebar_id] = array_values(array_unique($current));
$changed++;
}
wp_set_sidebars_widgets($assignments);
return ['sidebars' => $changed, 'widgets' => $created];
}
public static function preview(string $template): string {
$payload = self::template_payload($template);
$html = '';
foreach ($payload['sidebars'] as $sidebar_id => $items) {
$html .= '<section class="aksrt-widget-template-preview__area"><h4>' . esc_html((string) $sidebar_id) . '</h4>';
foreach ($items as $item) {
$definition = WidgetRegistry::get((string) ($item['widget'] ?? ''));
if ($definition === null) {
continue;
}
$settings = $definition->normalize((array) ($item['settings'] ?? []));
$html .= '<article class="aksrt-widget-template-preview__module aksrt-widget aksrt-widget--' . esc_attr($definition->slug()) . '"><strong>' . esc_html($definition->name()) . '</strong><div class="aksrt-widget__content">';
$html .= RendererRegistry::render($definition->renderer(), $settings, ['widget_id' => 'preview-0']);
$html .= '</div></article>';
}
$html .= '</section>';
}
return $html;
}
private static function export_widget(string $widget_id): ?array {
if (!preg_match('/^(aksrt_widget_[a-z0-9_]+)-([1-9][0-9]*)$/', $widget_id, $matches)) {
return null;
}
$definition = WidgetRegistry::get_by_id_base($matches[1]);
if ($definition === null) {
return null;
}
$settings = get_option('widget_' . $definition->id_base(), []);
$number = (int) $matches[2];
if (!is_array($settings) || !isset($settings[$number]) || !is_array($settings[$number])) {
return null;
}
return ['widget' => $definition->slug(), 'settings' => $definition->normalize($settings[$number])];
}
private static function create_widget(array $item): string {
$definition = WidgetRegistry::get(sanitize_key((string) ($item['widget'] ?? '')));
if ($definition === null) {
return '';
}
$option_name = 'widget_' . $definition->id_base();
$settings = get_option($option_name, []);
$settings = is_array($settings) ? $settings : [];
$numbers = array_filter(array_map('intval', array_keys($settings)), static fn(int $number): bool => $number > 0);
$number = $numbers ? max($numbers) + 1 : 1;
$settings[$number] = $definition->normalize((array) ($item['settings'] ?? []));
$settings['_multiwidget'] = 1;
update_option($option_name, $settings);
return $definition->id_base() . '-' . $number;
}
}
-245
View File
@@ -1,245 +0,0 @@
<?php
/**
* Custom buttons group (zibll zib_widget_ui_buttons).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class ButtonsWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_buttons', esc_html__('AKSRT 按钮组', 'aksrt'), [
'description' => esc_html__('显示一组自定义按钮,支持外链跳转与色系。', 'aksrt'),
'classname' => 'widget_aksrt_buttons',
]);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
// Also accept zibll-style `items`.
if (!$buttons && isset($instance['items']) && is_array($instance['items'])) {
$buttons = $instance['items'];
}
if (!$buttons) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一个按钮', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一个按钮', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$align = sanitize_key((string) ($instance['align'] ?? 'center'));
if (!in_array($align, ['left', 'center', 'right'], true)) {
$align = 'center';
}
$radius = !empty($instance['radius']);
$hollow = !empty($instance['hollow']);
$go_link = !empty($instance['go_link']);
$pc_gap = max(0, min(40, (int) ($instance['pc_gap'] ?? $instance['gap'] ?? 12)));
$m_gap = max(0, min(30, (int) ($instance['m_gap'] ?? 6)));
$pc_pad = sanitize_key((string) ($instance['pc_padding'] ?? 'large'));
$m_pad = sanitize_key((string) ($instance['m_padding'] ?? 'medium'));
if (!in_array($pc_pad, ['small', 'medium', 'large'], true)) {
$pc_pad = 'large';
}
if (!in_array($m_pad, ['small', 'medium', 'large'], true)) {
$m_pad = 'medium';
}
$pc_width = max(0, min(400, (int) ($instance['pc_width'] ?? 0)));
$m_width = max(0, min(400, (int) ($instance['m_width'] ?? 0)));
$but_class = 'but';
if ($radius) {
$but_class .= ' radius';
}
if ($hollow) {
$but_class .= ' hollow';
}
$buts = '';
foreach ($buttons as $btn) {
if (!is_array($btn)) {
continue;
}
$text = sanitize_text_field((string) ($btn['text'] ?? ''));
if ($text === '') {
continue;
}
$url = '';
$target = '';
if (is_array($btn['link'] ?? null)) {
$url = esc_url_raw(trim((string) ($btn['link']['url'] ?? '')));
$target = sanitize_text_field((string) ($btn['link']['target'] ?? ''));
} else {
$url = esc_url_raw(trim((string) ($btn['url'] ?? '')));
$target = !empty($btn['new_window']) || !empty($btn['target']) ? '_blank' : '';
}
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
if ($url === '') {
$url = 'javascript:void(0)';
}
$color = sanitize_html_class((string) ($btn['color'] ?? $btn['style'] ?? ''));
// Map legacy style keys to color classes.
$style_map = [
'primary' => 'c-blue',
'secondary' => 'c-blue-2',
'outline' => '',
'text' => '',
'pink' => 'c-red',
'blue' => 'c-blue',
'green' => 'c-green',
'yellow' => 'c-yellow',
'purple' => 'c-purple',
];
if (isset($style_map[$color])) {
$color = $style_map[$color];
}
$item_class = trim($but_class . ' ' . $color);
$icon_left = sanitize_text_field((string) ($btn['icon'] ?? $btn['icon_left'] ?? ''));
$icon_right = sanitize_text_field((string) ($btn['icon_right'] ?? ''));
$left_html = $icon_left !== '' ? '<i class="' . esc_attr($icon_left) . '" aria-hidden="true"></i> ' : '';
$right_html = $icon_right !== '' ? ' <span class="ml6"><i class="' . esc_attr($icon_right) . '" aria-hidden="true"></i></span>' : '';
$tgt = $target !== '' && strpos($url, 'javascript:') !== 0
? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"'
: '';
$buts .= '<div><a class="' . esc_attr($item_class) . '" href="' . esc_url($url) . '"' . $tgt . '>'
. $left_html . esc_html($text) . $right_html . '</a>';
$buts .= '</div>';
}
if ($buts === '') {
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$style_attr = '';
if ($pc_width > 0) {
$style_attr .= '--pc-width:' . $pc_width . 'px;';
}
if ($m_width > 0) {
$style_attr .= '--m-width:' . $m_width . 'px;';
}
$style_attr .= '--pc-gap:' . $pc_gap . 'px;--m-gap:' . $m_gap . 'px;--btn-gap:' . $pc_gap . 'px;';
echo '<div class="widget-buttons mb20 pc-size-' . esc_attr($pc_pad) . ' m-size-' . esc_attr($m_pad) . '">';
echo '<div class="widget-buttons-wrap flex hh align-' . esc_attr($align) . '" style="' . esc_attr($style_attr) . '">';
echo $buts; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$buttons = isset($instance['buttons']) && is_array($instance['buttons']) ? $instance['buttons'] : [];
while (count($buttons) < 4) {
$buttons[] = ['text' => '', 'url' => '', 'style' => 'primary', 'icon' => '', 'icon_right' => '', 'new_window' => false];
}
?>
<p>
<label><?php esc_html_e('标题(可选)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('对齐', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('align')); ?>">
<option value="center" <?php selected(($instance['align'] ?? 'center'), 'center'); ?>><?php esc_html_e('居中', 'aksrt'); ?></option>
<option value="left" <?php selected(($instance['align'] ?? ''), 'left'); ?>><?php esc_html_e('左', 'aksrt'); ?></option>
<option value="right" <?php selected(($instance['align'] ?? ''), 'right'); ?>><?php esc_html_e('右', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('radius')); ?>" value="1" <?php checked(!empty($instance['radius'])); ?>> <?php esc_html_e('圆角胶囊', 'aksrt'); ?></label>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hollow')); ?>" value="1" <?php checked(!empty($instance['hollow'])); ?>> <?php esc_html_e('镂空', 'aksrt'); ?></label>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
</p>
<?php foreach ($buttons as $i => $btn) : ?>
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][text]")); ?>" value="<?php echo esc_attr((string) ($btn['text'] ?? '')); ?>" placeholder="<?php esc_attr_e('按钮文字', 'aksrt'); ?>">
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][url]")); ?>" value="<?php echo esc_attr((string) ($btn['url'] ?? '')); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][icon]")); ?>" value="<?php echo esc_attr((string) ($btn['icon'] ?? '')); ?>" placeholder="<?php esc_attr_e('左图标 class', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][icon_right]")); ?>" value="<?php echo esc_attr((string) ($btn['icon_right'] ?? '')); ?>" placeholder="<?php esc_attr_e('右图标 class', 'aksrt'); ?>">
<select class="widefat" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][style]")); ?>">
<?php foreach (['primary' => '主要', 'secondary' => '次要', 'outline' => '线框', 'text' => '文字', 'pink' => '粉', 'blue' => '蓝', 'green' => '绿', 'yellow' => '黄', 'purple' => '紫'] as $k => $lab) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected(($btn['style'] ?? 'primary'), $k); ?>><?php echo esc_html($lab); ?></option>
<?php endforeach; ?>
</select>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name("buttons[{$i}][new_window]")); ?>" value="1" <?php checked(!empty($btn['new_window'])); ?>> <?php esc_html_e('新窗口', 'aksrt'); ?></label>
</div>
<?php endforeach;
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$buttons = [];
if (isset($new_instance['buttons']) && is_array($new_instance['buttons'])) {
foreach ($new_instance['buttons'] as $btn) {
if (!is_array($btn)) {
continue;
}
$text = sanitize_text_field((string) ($btn['text'] ?? ''));
if ($text === '') {
continue;
}
$buttons[] = [
'text' => $text,
'url' => esc_url_raw(trim((string) ($btn['url'] ?? ''))),
'style' => sanitize_key((string) ($btn['style'] ?? 'primary')),
'icon' => sanitize_text_field((string) ($btn['icon'] ?? '')),
'icon_right' => sanitize_text_field((string) ($btn['icon_right'] ?? '')),
'new_window' => !empty($btn['new_window']),
];
if (count($buttons) >= 8) {
break;
}
}
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'align' => in_array(($new_instance['align'] ?? ''), ['left', 'center', 'right'], true) ? $new_instance['align'] : 'center',
'radius' => !empty($new_instance['radius']) ? 1 : 0,
'hollow' => !empty($new_instance['hollow']) ? 1 : 0,
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
'gap' => max(4, min(30, (int) ($new_instance['gap'] ?? 12))),
'pc_gap' => max(0, min(60, (int) ($new_instance['pc_gap'] ?? $new_instance['gap'] ?? 12))),
'm_gap' => max(0, min(60, (int) ($new_instance['m_gap'] ?? 6))),
'pc_padding' => in_array(($new_instance['pc_padding'] ?? 'large'), ['small', 'medium', 'large'], true) ? $new_instance['pc_padding'] : 'large',
'm_padding' => in_array(($new_instance['m_padding'] ?? 'medium'), ['small', 'medium', 'large'], true) ? $new_instance['m_padding'] : 'medium',
'pc_width' => max(0, min(500, (int) ($new_instance['pc_width'] ?? 0))),
'm_width' => max(0, min(500, (int) ($new_instance['m_width'] ?? 0))),
'buttons' => $buttons,
]; return $business;
}
}
-263
View File
@@ -1,263 +0,0 @@
<?php
/**
* Categories list (zibll widget categories depth).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class CategoriesWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_categories',
esc_html__('AKSRT 分类目录', 'aksrt'),
[
'description' => esc_html__('以列表形式展示文章分类', 'aksrt'),
'customize_selective_refresh' => true,
'classname' => 'widget_aksrt_categories',
]
);
}
/** @return array<int,int> */
public static function parse_ids_static(string $raw): array {
if ($raw === '') {
return [];
}
$ids = [];
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
$id = (int) $p;
if ($id > 0) {
$ids[] = $id;
}
}
return array_values(array_unique($ids));
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$count = max(1, min(50, (int) ($instance['count'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
if (!in_array($orderby, ['count', 'name', 'id', 'slug'], true)) {
$orderby = 'count';
}
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
$hierarchical = !empty($instance['hierarchical']);
$parent = max(0, (int) ($instance['parent'] ?? 0));
$show_count = !isset($instance['show_count']) || !empty($instance['show_count']);
$show_desc = !empty($instance['show_desc']);
$show_thumb = !empty($instance['show_thumb']);
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$term_args = [
'taxonomy' => 'category',
'number' => $hierarchical ? 0 : $count,
'orderby' => $orderby,
'order' => $order,
'hide_empty' => $hide_empty,
'hierarchical' => $hierarchical,
];
if ($parent > 0) {
$term_args['parent'] = $parent;
} elseif ($hierarchical) {
$term_args['parent'] = 0;
}
if ($include) {
$term_args['include'] = $include;
$term_args['orderby'] = 'include';
unset($term_args['number']);
}
if ($exclude) {
$term_args['exclude'] = $exclude;
}
$cats = get_categories($term_args);
if (!$cats) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
ob_start();
$cls = 'widget-list aksrt-cat-list ajax-item' . ($hierarchical ? ' is-tree' : '') . ($show_thumb ? ' has-thumb' : '');
echo '<ul class="' . esc_attr($cls) . '">';
$n = 0;
foreach ($cats as $cat) {
if (!$hierarchical && $n >= $count) {
break;
}
echo self::render_cat_item($cat, $show_count, $show_desc, $show_thumb, $hierarchical ? 0 : -1, $count, $n, $hide_empty, $orderby, $order);
$n++;
}
echo '</ul>';
return (string) ob_get_clean();
}
/**
* @param \WP_Term $cat
*/
private static function render_cat_item($cat, bool $show_count, bool $show_desc, bool $show_thumb, int $depth, int $limit, int &$n, bool $hide_empty, string $orderby, string $order): string {
if ($n > 0 && $limit > 0 && $n >= $limit && $depth >= 0) {
return '';
}
$html = '<li class="aksrt-cat-list__item depth-' . max(0, $depth) . '">';
$html .= '<a class="aksrt-cat-list__link" href="' . esc_url(get_category_link($cat->term_id)) . '">';
if ($show_thumb) {
$thumb = '';
$img_id = (int) get_term_meta($cat->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($cat->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$thumb = (string) (wp_get_attachment_image_url($img_id, 'thumbnail') ?: '');
}
if ($thumb === '') {
$thumb = (string) get_term_meta($cat->term_id, '_aksrt_cover_url', true);
}
if ($thumb !== '') {
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
$html .= '<span class="aksrt-cat-list__thumb"><img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" width="32" height="32" /></span>';
} else {
$html .= '<span class="aksrt-cat-list__thumb is-empty"><i class="fa fa-folder-o" aria-hidden="true"></i></span>';
}
}
$html .= '<span class="aksrt-cat-list__text">';
$html .= '<span class="aksrt-cat-list__name">' . esc_html($cat->name) . '</span>';
if ($show_desc && $cat->description !== '') {
$desc = wp_trim_words(wp_strip_all_tags((string) $cat->description), 12, '…');
$html .= '<span class="aksrt-cat-list__desc muted-2-color em09">' . esc_html($desc) . '</span>';
}
$html .= '</span>';
if ($show_count) {
$html .= '<span class="count">' . (int) $cat->count . '</span>';
}
$html .= '</a>';
if ($depth >= 0) {
$children = get_categories([
'taxonomy' => 'category',
'parent' => (int) $cat->term_id,
'hide_empty' => $hide_empty,
'orderby' => $orderby,
'order' => $order,
]);
if ($children) {
$html .= '<ul class="aksrt-cat-list__children">';
foreach ($children as $child) {
$n++;
if ($limit > 0 && $n > $limit) {
break;
}
$html .= self::render_cat_item($child, $show_count, $show_desc, $show_thumb, $depth + 1, $limit, $n, $hide_empty, $orderby, $order);
}
$html .= '</ul>';
}
}
$html .= '</li>';
return $html;
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
?>
<p>
<label><?php esc_html_e('标题:', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? __('分类目录', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('显示数量:', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="50" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 8))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="count" <?php selected(($instance['orderby'] ?? 'count'), 'count'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
<option value="name" <?php selected(($instance['orderby'] ?? ''), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
<option value="id" <?php selected(($instance['orderby'] ?? ''), 'id'); ?>><?php esc_html_e('ID', 'aksrt'); ?></option>
<option value="slug" <?php selected(($instance['orderby'] ?? ''), 'slug'); ?>><?php esc_html_e('别名', 'aksrt'); ?></option>
</select>
</label>
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
<option value="DESC" <?php selected(($instance['order'] ?? 'DESC'), 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
<option value="ASC" <?php selected(($instance['order'] ?? ''), 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
</select>
</p>
<p>
<label><?php esc_html_e('父分类 ID(0=全部)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('parent')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['parent'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('包含 ID', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('include')); ?>" value="<?php echo esc_attr((string) ($instance['include'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排除 ID', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('exclude')); ?>" value="<?php echo esc_attr((string) ($instance['exclude'] ?? '')); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_count')); ?>" value="1" <?php checked(!isset($instance['show_count']) || !empty($instance['show_count'])); ?>> <?php esc_html_e('显示数量', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_desc')); ?>" value="1" <?php checked(!empty($instance['show_desc'])); ?>> <?php esc_html_e('显示描述', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示封面', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!isset($instance['hide_empty']) || !empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空分类', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hierarchical')); ?>" value="1" <?php checked(!empty($instance['hierarchical'])); ?>> <?php esc_html_e('层级树', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'count'));
if (!in_array($orderby, ['count', 'name', 'id', 'slug'], true)) {
$orderby = 'count';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(50, (int) ($new_instance['count'] ?? 8))),
'orderby' => $orderby,
'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
'parent' => max(0, (int) ($new_instance['parent'] ?? 0)),
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'exclude' => sanitize_text_field((string) ($new_instance['exclude'] ?? '')),
'show_count' => !empty($new_instance['show_count']) ? 1 : 0,
'show_desc' => !empty($new_instance['show_desc']) ? 1 : 0,
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
'hierarchical' => !empty($new_instance['hierarchical']) ? 1 : 0,
];
}
}
-518
View File
@@ -1,518 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Homepage content widgets aligned to the fixed Zibll-like layout.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_home_widget_allowed_layouts')) {
/**
* Allowed post-card layouts for homepage content widgets.
*
* @return array<string, string>
*/
function aksrt_home_widget_allowed_layouts(): array {
return [
'card' => __('卡片', 'aksrt'),
'list' => __('列表', 'aksrt'),
'grid-cms' => __('资源网格', 'aksrt'),
];
}
}
if (!function_exists('aksrt_home_widget_post_types')) {
/**
* Post types used by homepage widgets.
*
* @return array<int, string>
*/
function aksrt_home_widget_post_types(): array {
return ['post'];
}
}
if (!function_exists('aksrt_home_widget_available_categories')) {
/**
* Return categories for widget admin forms.
*
* @return array<int, \WP_Term>
*/
function aksrt_home_widget_available_categories(): array {
$categories = get_categories([
'taxonomy' => 'category',
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
]);
return is_array($categories) ? $categories : [];
}
}
if (!function_exists('aksrt_home_widget_fallback_category_ids')) {
/**
* Pick top categories as fallback when widget instance has no selection.
*
* @param int $limit Maximum category ids to return.
* @return array<int, int>
*/
function aksrt_home_widget_fallback_category_ids(int $limit = 1): array {
$limit = max(1, min(8, $limit));
$terms = get_terms([
'taxonomy' => 'category',
'orderby' => 'count',
'order' => 'DESC',
'number' => $limit,
'hide_empty' => true,
'fields' => 'ids',
]);
if (is_wp_error($terms) || !is_array($terms)) {
return [];
}
return array_values(array_filter(array_map('absint', $terms)));
}
}
if (!function_exists('aksrt_home_widget_normalize_category_ids')) {
/**
* Normalize selected category ids.
*
* @param mixed $raw_ids Raw ids from widget instance.
* @param int $limit Maximum ids to keep.
* @return array<int, int>
*/
function aksrt_home_widget_normalize_category_ids($raw_ids, int $limit = 4): array {
$ids = array_map('absint', (array) $raw_ids);
$ids = array_values(array_filter($ids, static fn($id) => $id > 0));
if ($limit > 0) {
$ids = array_slice($ids, 0, $limit);
}
return $ids;
}
}
if (!function_exists('aksrt_home_widget_resolve_layout')) {
/**
* Sanitize requested widget post layout.
*
* @param mixed $layout Layout value.
* @param string $default Default layout.
* @return string
*/
function aksrt_home_widget_resolve_layout($layout, string $default = 'card'): string {
$allowed = array_keys(aksrt_home_widget_allowed_layouts());
$layout = is_string($layout) ? $layout : $default;
return in_array($layout, $allowed, true) ? $layout : $default;
}
}
if (!function_exists('aksrt_home_widget_resolve_context')) {
/**
* Resolve widget area context from sidebar args.
*/
function aksrt_home_widget_resolve_context(array $args): string {
if (function_exists('aksrt_widget_get_area_context')) {
return aksrt_widget_get_area_context($args);
}
return 'homepage';
}
}
if (!function_exists('aksrt_home_widget_effective_layout')) {
/**
* Compact rich homepage widgets when placed into a sidebar area.
*/
function aksrt_home_widget_effective_layout(string $layout, string $context): string {
if ($context === 'sidebar' && $layout !== 'list') {
return 'list';
}
return $layout;
}
}
if (!function_exists('aksrt_home_widget_effective_columns')) {
/**
* Sidebar widgets should stay single-column.
*/
function aksrt_home_widget_effective_columns(int $columns, string $context): int {
if ($context === 'sidebar') {
return 1;
}
return max(1, min(4, $columns));
}
}
if (!function_exists('aksrt_home_widget_render_header')) {
/**
* Render a compact Zibll-like section header.
*
* @param string $title Section title.
* @param string $more_link Optional more link.
* @param string $more_text Optional more text.
* @return void
*/
function aksrt_home_widget_render_header(string $title, string $more_link = '', string $more_text = ''): void {
?>
<header class="aksrt-home-widget__header">
<h3 class="aksrt-home-widget__title"><?php echo esc_html($title); ?></h3>
<?php if ($more_link !== '') : ?>
<a class="aksrt-home-widget__more" href="<?php echo esc_url($more_link); ?>">
<?php echo esc_html($more_text !== '' ? $more_text : __('查看更多', 'aksrt')); ?>
</a>
<?php endif; ?>
</header>
<?php
}
}
if (!function_exists('aksrt_home_widget_render_post_grid')) {
/**
* Render posts for homepage widgets.
*
* Delegates to the unified shared renderer (aksrt_render_post_list).
*
* @param \WP_Query $query Posts query.
* @param string $layout Card layout.
* @param int $columns Desktop columns.
* @param string $context Sidebar or homepage context.
* @return void
*/
function aksrt_home_widget_render_post_grid(\WP_Query $query, string $layout = 'card', int $columns = 3, string $context = 'homepage'): void {
if (!$query->have_posts()) {
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无内容', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无内容', 'aksrt') . '</p>';
wp_reset_postdata();
return;
}
if ($context === 'sidebar') {
$use_homepage_mini = function_exists('aksrt_is_fixed_homepage_request')
&& aksrt_is_fixed_homepage_request()
&& function_exists('aksrt_render_mini_list')
&& (!class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::homepage_sidebar_mini_enabled());
if ($use_homepage_mini && function_exists('aksrt_render_mini_list')) {
echo '<div class="widget-tab-posts__list widget-tab-posts__list--mini aksrt-home-widget__sidebar-list">';
aksrt_render_mini_list($query, ['context' => 'sidebar', 'show_thumb' => true]);
echo '</div>';
return;
}
// Fallback: shared mini list (lazy thumbs + meta).
if (function_exists('aksrt_render_mini_list')) {
echo '<div class="widget-tab-posts__list widget-tab-posts__list--mini aksrt-home-widget__sidebar-list">';
aksrt_render_mini_list($query, ['context' => 'sidebar', 'show_thumb' => true]);
echo '</div>';
return;
}
echo '<ul class="widget-tab-posts__list aksrt-home-widget__sidebar-list">';
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
while ($query->have_posts()) {
$query->the_post();
$thumb = get_the_post_thumbnail_url(get_the_ID(), 'thumbnail');
echo '<li class="widget-tab-posts__item">';
if ($thumb) {
echo '<a href="' . esc_url(get_permalink()) . '" class="widget-tab-posts__thumb"><img class="fit-cover aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($ph) . '" alt="" loading="lazy" /></a>';
}
echo '<div class="widget-tab-posts__info">';
echo '<a href="' . esc_url(get_permalink()) . '" class="widget-tab-posts__title">' . esc_html(get_the_title()) . '</a>';
echo '<span class="widget-tab-posts__meta">' . esc_html(human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt')) . '</span>';
echo '</div></li>';
}
echo '</ul>';
wp_reset_postdata();
return;
}
// Homepage / content area: delegate to shared renderer
$layout = aksrt_home_widget_resolve_layout($layout, 'card');
$columns = max(1, min(4, $columns));
if (function_exists('aksrt_render_post_list')) {
aksrt_render_post_list($query, [
'mode' => $layout === 'grid-cms' ? 'grid-cms' : $layout,
'context' => 'widget',
'columns' => $columns,
], true);
} else {
// Fallback: use template part
$previous_layout = get_query_var('aksrt_post_card_layout', '');
$previous_scene = get_query_var('aksrt_post_card_scene', '');
set_query_var('aksrt_post_card_layout', $layout);
set_query_var('aksrt_post_card_scene', 'home');
$classes = [
'aksrt-home-widget__post-grid',
'aksrt-home-widget__post-grid--' . $layout,
'post-grid',
'post-grid--' . $layout,
];
printf(
'<div class="%1$s" style="--aksrt-widget-columns:%2$d;">',
esc_attr(implode(' ', $classes)),
(int) $columns
);
while ($query->have_posts()) {
$query->the_post();
get_template_part('templates/parts/content', 'post-card');
}
echo '</div>';
set_query_var('aksrt_post_card_layout', $previous_layout);
set_query_var('aksrt_post_card_scene', $previous_scene);
}
wp_reset_postdata();
}
}
if (!function_exists('aksrt_home_widget_get_lazyload_args')) {
/**
* Lazyload settings reused by category recommend widget.
*
* @return array{enabled: bool, placeholder_url: string}
*/
function aksrt_home_widget_get_lazyload_args(): array {
$config = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_lazyload_config()
: ['enabled' => false, 'placeholder_url' => ''];
return [
'enabled' => ! empty($config['enabled']),
'placeholder_url' => ! empty($config['placeholder_url'])
? (string) $config['placeholder_url']
: get_template_directory_uri() . '/assets/img/placeholder.svg',
];
}
}
/**
* Homepage category block widget.
*/
class CategoryBlockWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_category_block',
esc_html__('AKSRT 分类块', 'aksrt'),
[
'classname' => 'widget_aksrt_category_block',
'description' => esc_html__('指定分类的最新内容区块,可挂载到首页内容挂载区。', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
/**
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
*/
public static function query_payload(array $instance, int $page = 1, string $context = 'homepage'): array {
$page = max(1, $page);
$category_id = self::resolve_category_id_static($instance);
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
$layout = aksrt_home_widget_effective_layout(
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
$context
);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['ajax', 'none', 'number'], true)) {
$paginate = 'none';
}
$query_args = [
'post_type' => aksrt_home_widget_post_types(),
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => $page,
'ignore_sticky_posts' => true,
];
if ($paginate === 'none') {
$query_args['no_found_rows'] = true;
}
if ($category_id > 0) {
$query_args['cat'] = $category_id;
}
$query = new \WP_Query($query_args);
ob_start();
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
$html = (string) ob_get_clean();
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('暂无内容', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无内容', 'aksrt') . '</p>';
}
$max_pages = max(1, (int) $query->max_num_pages);
$total = (int) $query->found_posts;
$has_more = in_array($paginate, ['ajax', 'number'], true) && $page < $max_pages;
$base_qs = [
'count' => $count,
'cat' => $category_id,
'columns' => $columns,
'layout' => $layout,
'context' => $context,
'paginate' => $paginate,
];
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/category-block'));
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg(['page' => $page + 1], $rest_base);
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
} elseif ($paginate === 'number' && $max_pages > 1) {
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
} else {
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total');
}
public function widget($args, $instance): void {
$context = aksrt_home_widget_resolve_context($args);
$category_id = self::resolve_category_id_static($instance);
$term = $category_id > 0 ? get_term($category_id, 'category') : null;
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : (($term && ! is_wp_error($term)) ? $term->name : __('精选分类', 'aksrt'));
$show_more = ! empty($instance['show_more']);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['ajax', 'none', 'number'], true)) {
$paginate = 'none';
}
$instance['paginate'] = $paginate;
$payload = self::query_payload($instance, 1, $context);
echo $args['before_widget'];
echo '<section class="aksrt-home-widget aksrt-home-widget--category-block">';
aksrt_home_widget_render_header(
(string) $title,
($show_more && $term && ! is_wp_error($term)) ? (string) get_category_link($category_id) : '',
__('查看分类', 'aksrt')
);
$rest = rest_url('aksrt/v1/widgets/category-block');
echo '<div class="ajaxpager aksrt-home-widget__ajax" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
echo '</div>';
echo '</section>';
echo $args['after_widget'];
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$category_id = (int) ($instance['category_id'] ?? 0);
$count = (int) ($instance['count'] ?? 6);
$columns = (int) ($instance['columns'] ?? 3);
$layout = aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card');
$show_more = ! empty($instance['show_more']);
$categories = aksrt_home_widget_available_categories();
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('category_id')); ?>"><?php esc_html_e('分类:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_id')); ?>" name="<?php echo esc_attr($this->get_field_name('category_id')); ?>">
<option value="0"><?php esc_html_e('自动选择最热门分类', 'aksrt'); ?></option>
<?php foreach ($categories as $category) : ?>
<option value="<?php echo (int) $category->term_id; ?>" <?php selected($category_id, (int) $category->term_id); ?>>
<?php echo esc_html($category->name); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('文章数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="12" value="<?php echo (int) $count; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('layout')); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('layout')); ?>" name="<?php echo esc_attr($this->get_field_name('layout')); ?>">
<?php foreach (aksrt_home_widget_allowed_layouts() as $value => $label) : ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($layout, $value); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示分类链接', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
<?php
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'category_id' => absint($new_instance['category_id'] ?? 0),
'count' => max(1, min(12, absint($new_instance['count'] ?? 6))),
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
'paginate' => in_array(sanitize_key((string) ($new_instance['paginate'] ?? 'none')), ['none', 'ajax', 'number'], true) ? sanitize_key((string) ($new_instance['paginate'] ?? 'none')) : 'none',
]; return $business;
}
public static function resolve_category_id_static(array $instance): int {
$category_id = absint($instance['category_id'] ?? 0);
if ($category_id > 0) {
return $category_id;
}
$fallback = aksrt_home_widget_fallback_category_ids(1);
return $fallback[0] ?? 0;
}
private function resolve_category_id(array $instance): int {
return self::resolve_category_id_static($instance);
}
}
-264
View File
@@ -1,264 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Homepage category recommend cards widget.
*/
class CategoryRecommendWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_category_recommend',
esc_html__('AKSRT 分类推荐卡片', 'aksrt'),
[
'classname' => 'widget_aksrt_category_recommend',
'description' => esc_html__('分类推荐卡片区块,可挂载到首页内容挂载区。', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public static function render_html(array $instance): string {
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 4);
$posts_per_cat = max(1, min(8, (int) ($instance['posts_per_category'] ?? 5)));
$columns = max(1, min(4, (int) ($instance['columns'] ?? 2)));
$show_views = ! empty($instance['show_views']);
$show_description = ! empty($instance['show_description']);
$show_dates = ! empty($instance['show_dates']);
$show_more = ! empty($instance['show_more']);
if (empty($category_ids)) {
$category_ids = aksrt_home_widget_fallback_category_ids(4);
}
$cards = self::build_cards_static($category_ids, $posts_per_cat, $show_views, $show_description);
if (empty($cards)) {
return function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('暂无推荐分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无推荐分类', 'aksrt') . '</p>';
}
$lazy = aksrt_home_widget_get_lazyload_args();
ob_start();
printf('<div class="aksrt-category-recommend__grid ajax-item" style="--aksrt-recommend-columns:%d;">', (int) $columns);
foreach ($cards as $card) {
$cover = (string) ($card['cover'] ?? '');
$link = (string) ($card['link'] ?? '');
$name = (string) ($card['name'] ?? '');
$badge = sprintf(_n('%d 篇文章', '%d 篇文章', (int) $card['count'], 'aksrt'), (int) $card['count']);
$more = '';
if ($show_views && (int) $card['views'] > 0) {
$v = function_exists('aksrt_format_number') ? aksrt_format_number((int) $card['views']) : number_format_i18n((int) $card['views']);
$more = '<span class="aksrt-category-recommend__views"><i class="fa fa-eye" aria-hidden="true"></i> ' . esc_html((string) $v) . '</span>';
}
echo '<article class="aksrt-category-recommend__card" data-aksrt-animate="fade-up">';
if (function_exists('aksrt_graphic_card') && $cover !== '') {
echo aksrt_graphic_card([ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'type' => 'style-2',
'class' => 'aksrt-category-recommend__hero mb10',
'img' => $cover,
'alt' => $name,
'link' => ['url' => $link],
'text1' => $name,
'text2' => $show_description ? (string) $card['description'] : '',
'text3' => $badge,
'more' => $more,
'lazy' => true,
]);
} else {
echo '<div class="aksrt-category-recommend__hero">';
echo '<a href="' . esc_url($link) . '">';
if ($lazy['enabled']) {
echo '<span class="aksrt-skeleton"></span>';
echo '<img class="aksrt-category-recommend__cover aksrt-lazy" data-src="' . esc_url($cover) . '" src="' . esc_url($lazy['placeholder_url']) . '" alt="' . esc_attr($name) . '">';
} else {
echo '<img class="aksrt-category-recommend__cover" src="' . esc_url($cover) . '" alt="' . esc_attr($name) . '">';
}
echo '</a>';
echo '<span class="aksrt-category-recommend__badge">' . esc_html($badge) . '</span>';
echo $more; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo '<div class="aksrt-category-recommend__meta">';
echo '<h4 class="aksrt-category-recommend__name"><a href="' . esc_url($link) . '">' . esc_html($name) . '</a></h4>';
if ($show_description && $card['description'] !== '') {
echo '<p class="aksrt-category-recommend__desc">' . esc_html((string) $card['description']) . '</p>';
}
echo '</div>';
}
echo '<div class="aksrt-category-recommend__body">';
// Prefer shared mini list when post IDs available.
if (!empty($card['post_ids']) && function_exists('aksrt_render_mini_list')) {
$q = new \WP_Query([
'post_type' => 'any',
'post__in' => array_map('intval', (array) $card['post_ids']),
'orderby' => 'post__in',
'posts_per_page' => count((array) $card['post_ids']),
'ignore_sticky_posts' => true,
'no_found_rows' => true,
]);
echo '<div class="aksrt-category-recommend__posts posts-mini-wrap">';
aksrt_render_mini_list($q, ['context' => 'widget', 'show_thumb' => true, 'show_meta' => $show_dates]);
echo '</div>';
wp_reset_postdata();
} else {
echo '<ul class="aksrt-category-recommend__posts">';
foreach ($card['posts'] as $post_item) {
echo '<li class="aksrt-category-recommend__post">';
echo '<a class="aksrt-category-recommend__post-link" href="' . esc_url($post_item['link']) . '">' . esc_html($post_item['title']) . '</a>';
if ($show_dates && $post_item['date'] !== '') {
echo '<span class="aksrt-category-recommend__date">' . esc_html($post_item['date']) . '</span>';
}
echo '</li>';
}
echo '</ul>';
}
if ($show_more) {
echo '<div class="aksrt-home-widget__footer">';
echo '<a class="aksrt-home-widget__more" href="' . esc_url($link) . '">' . esc_html__('查看更多', 'aksrt') . '</a>';
echo '</div>';
}
echo '</div></article>';
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : __('分类推荐', 'aksrt');
$html = self::render_html($instance);
echo $args['before_widget'];
echo '<section class="aksrt-home-widget aksrt-home-widget--recommend">';
aksrt_home_widget_render_header($title);
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</section>';
echo $args['after_widget'];
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? __('分类推荐', 'aksrt'));
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 4);
$posts_per_cat = (int) ($instance['posts_per_category'] ?? 5);
$columns = (int) ($instance['columns'] ?? 2);
$show_views = ! empty($instance['show_views']);
$show_description = ! array_key_exists('show_description', $instance) || ! empty($instance['show_description']);
$show_dates = ! array_key_exists('show_dates', $instance) || ! empty($instance['show_dates']);
$show_more = ! array_key_exists('show_more', $instance) || ! empty($instance['show_more']);
$categories = aksrt_home_widget_available_categories();
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('category_ids')); ?>"><?php esc_html_e('分类(最多 4 个):', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('category_ids')); ?>[]" multiple size="6">
<?php foreach ($categories as $category) : ?>
<option value="<?php echo (int) $category->term_id; ?>" <?php selected(in_array((int) $category->term_id, $category_ids, true)); ?>>
<?php echo esc_html($category->name); ?>
</option>
<?php endforeach; ?>
</select>
<small><?php esc_html_e('留空时自动选择热门分类。', 'aksrt'); ?></small>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('posts_per_category')); ?>"><?php esc_html_e('每个分类展示文章数:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('posts_per_category')); ?>" name="<?php echo esc_attr($this->get_field_name('posts_per_category')); ?>" type="number" min="1" max="8" value="<?php echo (int) $posts_per_cat; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_views); ?> id="<?php echo esc_attr($this->get_field_id('show_views')); ?>" name="<?php echo esc_attr($this->get_field_name('show_views')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_views')); ?>"><?php esc_html_e('显示分类总浏览量', 'aksrt'); ?></label>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_description); ?> id="<?php echo esc_attr($this->get_field_id('show_description')); ?>" name="<?php echo esc_attr($this->get_field_name('show_description')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_description')); ?>"><?php esc_html_e('显示分类描述', 'aksrt'); ?></label>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_dates); ?> id="<?php echo esc_attr($this->get_field_id('show_dates')); ?>" name="<?php echo esc_attr($this->get_field_name('show_dates')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_dates')); ?>"><?php esc_html_e('显示文章时间', 'aksrt'); ?></label>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示“查看更多”链接', 'aksrt'); ?></label>
</p>
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('homepage'); } ?>
<?php
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'category_ids' => aksrt_home_widget_normalize_category_ids($new_instance['category_ids'] ?? [], 4),
'posts_per_category' => max(1, min(8, absint($new_instance['posts_per_category'] ?? 5))),
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 2))),
'show_views' => ! empty($new_instance['show_views']) ? 1 : 0,
'show_description' => ! empty($new_instance['show_description']) ? 1 : 0,
'show_dates' => ! empty($new_instance['show_dates']) ? 1 : 0,
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
]; return $business;
}
/**
* Build category recommend cards.
*
* @param array<int, int> $category_ids Category ids.
* @param int $posts_per_cat Post count per category.
* @param bool $show_views Whether to calculate views.
* @param bool $show_description Whether to fetch description.
* @return array<int, array<string, mixed>>
*/
public static function build_cards_static(array $category_ids, int $posts_per_cat, bool $show_views, bool $show_description): array {
$cards = [];
foreach ($category_ids as $category_id) {
$term = get_term($category_id, 'category');
if (! $term || is_wp_error($term)) {
continue;
}
$posts = get_posts([
'post_type' => aksrt_home_widget_post_types(),
'cat' => $category_id,
'posts_per_page' => $posts_per_cat,
'post_status' => 'publish',
'orderby' => 'date',
'order' => 'DESC',
'no_found_rows' => true,
]);
$post_items = [];
foreach ($posts as $post) {
$post_items[] = [
'title' => get_the_title($post->ID),
'link' => get_permalink($post->ID),
'date' => function_exists('aksrt_time_ago') ? aksrt_time_ago($post->post_date) : get_the_date('Y-m-d', $post->ID),
];
}
$cards[] = [
'name' => $term->name,
'link' => get_category_link($category_id),
'cover' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_category_cover_url($category_id) : (get_template_directory_uri() . '/assets/img/placeholder.svg'),
'description' => $show_description && class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_category_recommend_desc($category_id) : ($show_description ? (string) $term->description : ''),
'count' => (int) $term->count,
'views' => $show_views && function_exists('aksrt_get_category_total_views') ? aksrt_get_category_total_views($category_id) : 0,
'posts' => $post_items,
];
}
return $cards;
}
}
-203
View File
@@ -1,203 +0,0 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
/**
* Homepage category tabs widget.
*/
class CategoryTabsWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_category_tabs',
esc_html__('AKSRT 分类 Tabs', 'aksrt'),
[
'classname' => 'widget_aksrt_category_tabs',
'description' => esc_html__('多分类切换内容流,可挂载到首页内容挂载区。', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public function widget($args, $instance): void {
$context = aksrt_home_widget_resolve_context($args);
$title = apply_filters('widget_title', $instance['title'] ?? '', $instance, $this->id_base);
$title = $title !== '' ? $title : __('热门分类', 'aksrt');
$count = max(1, min(12, (int) ($instance['count'] ?? 6)));
$columns = aksrt_home_widget_effective_columns((int) ($instance['columns'] ?? 3), $context);
$layout = aksrt_home_widget_effective_layout(
aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card'),
$context
);
$show_more = ! empty($instance['show_more']);
$tabs = $this->resolve_tabs($instance);
echo $args['before_widget'];
echo '<section class="aksrt-home-widget aksrt-home-widget--tabs widget-tab-posts" data-aksrt-home-cat-tabs data-aksrt-tabs="1" data-rest="' . esc_url(rest_url('aksrt/v1/widgets/category-tabs')) . '" data-count="' . esc_attr((string) $count) . '" data-layout="' . esc_attr($layout) . '" data-columns="' . esc_attr((string) $columns) . '" data-context="' . esc_attr($context) . '" data-show-more="' . (!empty($show_more) ? '1' : '0') . '">';
if (empty($tabs)) {
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无可用分类', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无可用分类', 'aksrt') . '</p>';
echo '</section>';
echo $args['after_widget'];
return;
}
?>
<header class="aksrt-home-widget__header aksrt-home-widget__header--tabs">
<h3 class="aksrt-home-widget__title"><?php echo esc_html($title); ?></h3>
<ul class="widget-tab-posts__nav aksrt-home-widget__tab-nav" role="tablist">
<?php foreach ($tabs as $index => $tab) : ?>
<li role="presentation">
<button
type="button"
role="tab"
class="widget-tab-posts__tab<?php echo 0 === $index ? ' active' : ''; ?>"
data-tab="cat-<?php echo (int) $tab['id']; ?>"
aria-selected="<?php echo 0 === $index ? 'true' : 'false'; ?>"
>
<?php echo esc_html($tab['name']); ?>
</button>
</li>
<?php endforeach; ?>
</ul>
</header>
<?php
foreach ($tabs as $index => $tab) {
?>
<div class="widget-tab-posts__panel<?php echo 0 === $index ? ' active' : ''; ?>"
data-tab-panel="cat-<?php echo (int) $tab['id']; ?>"
data-loaded="<?php echo 0 === $index ? '1' : '0'; ?>"
data-cat="<?php echo (int) $tab['id']; ?>"
data-more="<?php echo esc_url($tab['link']); ?>"
role="tabpanel"<?php echo 0 === $index ? '' : ' hidden'; ?>>
<?php if (0 === $index) :
$query = new \WP_Query([
'post_type' => aksrt_home_widget_post_types(),
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'cat' => (int) $tab['id'],
]);
aksrt_home_widget_render_post_grid($query, $layout, $columns, $context);
wp_reset_postdata();
if ($show_more) : ?>
<div class="aksrt-home-widget__footer">
<a class="aksrt-home-widget__more" href="<?php echo esc_url($tab['link']); ?>">
<?php esc_html_e('查看更多', 'aksrt'); ?>
</a>
</div>
<?php endif;
else : ?>
<div class="post_ajax_loader" data-aksrt-ajax-loader>
<?php for ($si = 0; $si < $count; $si++) : ?>
<div class="posts-mini placeholder-row"><div class="placeholder radius thumb-placeholder"></div><div class="flex1"><div class="placeholder k1"></div><div class="placeholder s1 mt6"></div></div></div>
<?php endfor; ?>
</div>
<?php endif; ?>
</div>
<?php
}
echo '</section>';
echo $args['after_widget'];
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 6);
$count = (int) ($instance['count'] ?? 6);
$columns = (int) ($instance['columns'] ?? 3);
$layout = aksrt_home_widget_resolve_layout($instance['layout'] ?? 'card', 'card');
$show_more = ! empty($instance['show_more']);
$categories = aksrt_home_widget_available_categories();
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('category_ids')); ?>"><?php esc_html_e('分类(可多选):', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('category_ids')); ?>" name="<?php echo esc_attr($this->get_field_name('category_ids')); ?>[]" multiple size="6">
<?php foreach ($categories as $category) : ?>
<option value="<?php echo (int) $category->term_id; ?>" <?php selected(in_array((int) $category->term_id, $category_ids, true)); ?>>
<?php echo esc_html($category->name); ?>
</option>
<?php endforeach; ?>
</select>
<small><?php esc_html_e('留空时自动选择热门分类。', 'aksrt'); ?></small>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('每个 Tab 的文章数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="12" value="<?php echo (int) $count; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('columns')); ?>"><?php esc_html_e('桌面列数:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('columns')); ?>" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" type="number" min="1" max="4" value="<?php echo (int) $columns; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('layout')); ?>"><?php esc_html_e('布局:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('layout')); ?>" name="<?php echo esc_attr($this->get_field_name('layout')); ?>">
<?php foreach (aksrt_home_widget_allowed_layouts() as $value => $label) : ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($layout, $value); ?>>
<?php echo esc_html($label); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<input class="checkbox" type="checkbox" <?php checked($show_more); ?> id="<?php echo esc_attr($this->get_field_id('show_more')); ?>" name="<?php echo esc_attr($this->get_field_name('show_more')); ?>" value="1">
<label for="<?php echo esc_attr($this->get_field_id('show_more')); ?>"><?php esc_html_e('显示“查看更多”链接', 'aksrt'); ?></label>
</p>
<?php if (function_exists('aksrt_widget_render_location_help')) { aksrt_widget_render_location_help('hybrid'); } ?>
<?php
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'category_ids' => aksrt_home_widget_normalize_category_ids($new_instance['category_ids'] ?? [], 6),
'count' => max(1, min(12, absint($new_instance['count'] ?? 6))),
'columns' => max(1, min(4, absint($new_instance['columns'] ?? 3))),
'layout' => aksrt_home_widget_resolve_layout($new_instance['layout'] ?? 'card', 'card'),
'show_more' => ! empty($new_instance['show_more']) ? 1 : 0,
]; return $business;
}
/**
* Resolve configured category tabs.
*
* @param array $instance Widget instance.
* @return array<int, array{id:int,name:string,link:string}>
*/
private function resolve_tabs(array $instance): array {
$category_ids = aksrt_home_widget_normalize_category_ids($instance['category_ids'] ?? [], 6);
if (empty($category_ids)) {
$category_ids = aksrt_home_widget_fallback_category_ids(4);
}
$tabs = [];
foreach ($category_ids as $category_id) {
$term = get_term($category_id, 'category');
if (! $term || is_wp_error($term)) {
continue;
}
$tabs[] = [
'id' => (int) $category_id,
'name' => $term->name,
'link' => get_category_link($category_id),
];
}
return $tabs;
}
}
-11
View File
@@ -1,11 +0,0 @@
<?php
/** Canonical shared widget field contract. */
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
final class CommonWidgetFields {
public static function defaults(): array { return ['subtitle'=>'','title_link'=>'','display_pc'=>1,'display_mobile'=>1,'sticky'=>0,'animation'=>'none','background'=>'surface','dark_background'=>'surface']; }
public static function normalize(array $instance): array {
$d=self::defaults(); $out=$instance; $out['subtitle']=sanitize_text_field((string)($instance['subtitle']??$d['subtitle'])); $out['title_link']=esc_url_raw((string)($instance['title_link']??'')); foreach(['display_pc','display_mobile','sticky'] as $k)$out[$k]=!empty($instance[$k])?1:0; $a=sanitize_key((string)($instance['animation']??'none')); $out['animation']=in_array($a,['none','fade','slide'],true)?$a:'none'; $b=sanitize_key((string)($instance['background']??'surface')); $out['background']=in_array($b,['surface','transparent','primary'],true)?$b:'surface'; $db=sanitize_key((string)($instance['dark_background']??'surface')); $out['dark_background']=in_array($db,['surface','transparent','primary'],true)?$db:'surface'; return $out;
}
}
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-276
View File
@@ -1,276 +0,0 @@
<?php
/**
* Hot posts ranking (zibll hot posts + ajax).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class HotPostsWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_hot_posts',
esc_html__('AKSRT 热榜文章', 'aksrt'),
[
'description' => esc_html__('按浏览量/评论数/点赞数排序的热门文章列表', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public static function build_query_args(array $instance, int $paged = 1): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$order_by = sanitize_key((string) ($instance['order_by'] ?? 'views'));
$days = max(0, (int) ($instance['days'] ?? 30));
$query_args = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => max(1, $paged),
'ignore_sticky_posts' => true,
];
if ($days > 0 && $days < 365) {
$query_args['date_query'] = [['after' => $days . ' days ago']];
}
switch ($order_by) {
case 'comments':
$query_args['orderby'] = 'comment_count';
$query_args['order'] = 'DESC';
break;
case 'likes':
$query_args['meta_key'] = '_aksrt_like_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'favorites':
$query_args['meta_key'] = '_aksrt_favorite_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'downloads':
$query_args['meta_key'] = '_aksrt_res_download_count';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
case 'views':
default:
$query_args['meta_key'] = 'aksrt_post_views';
$query_args['orderby'] = 'meta_value_num';
$query_args['order'] = 'DESC';
break;
}
return $query_args;
}
public static function render_items_html(\WP_Query $query, array $instance, int $start = 0): string {
if (!$query->have_posts()) {
return '';
}
$show_thumb = !isset($instance['show_thumb']) || !empty($instance['show_thumb']);
$order_by = sanitize_key((string) ($instance['order_by'] ?? 'views'));
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
$top_tones = ['jb-red', 'jb-yellow', 'b-gray'];
ob_start();
$index = $start;
while ($query->have_posts()) {
$query->the_post();
$index++;
$pid = (int) get_the_ID();
if ($order_by === 'comments') {
$metric = (int) get_comments_number($pid);
$metric_icon = 'fa-comment-o';
$metric_label = __('条评论', 'aksrt');
} elseif ($order_by === 'likes') {
$metric = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($pid) : (int) get_post_meta($pid, '_aksrt_like_count', true);
$metric_icon = 'fa-heart';
$metric_label = __('人点赞', 'aksrt');
} elseif ($order_by === 'favorites') {
$metric = (int) get_post_meta($pid, '_aksrt_favorite_count', true);
$metric_icon = 'fa-star';
$metric_label = __('人收藏', 'aksrt');
} elseif ($order_by === 'downloads') {
$metric = (int) get_post_meta($pid, '_aksrt_res_download_count', true);
$metric_icon = 'fa-download';
$metric_label = __('次下载', 'aksrt');
} else {
$metric = (int) get_post_meta($pid, 'aksrt_post_views', true);
$metric_icon = 'fa-eye';
$metric_label = __('次阅读', 'aksrt');
}
$metric_txt = function_exists('aksrt_format_number') ? aksrt_format_number($metric) : (string) $metric;
$tone = $top_tones[$index - 1] ?? 'b-gray';
$top_badge = '<badge class="img-badge left hot ' . ($index === 1 ? 'em12 ' : '') . esc_attr($tone) . '"><i>TOP' . (int) $index . '</i></badge>';
$permalink = get_permalink($pid);
$title = get_the_title($pid);
$time_ago = human_time_diff(get_the_time('U'), current_time('timestamp')) . __('前', 'aksrt');
$tgt = !empty($instance['target_blank']) ? ' target="_blank" rel="noopener noreferrer"' : '';
// Graphic hero for #1 (zibll zib_hot_posts).
if ($index === 1 && $start === 0) {
$thumb = get_the_post_thumbnail_url($pid, 'large');
if (!$thumb) {
$thumb = get_the_post_thumbnail_url($pid, 'medium');
}
echo '<div class="hot-posts-graphic relative ajax-item is-top1">';
echo '<a href="' . esc_url($permalink) . '"' . $tgt . '>';
echo '<div class="graphic hover-zoom-img" style="padding-bottom:60%;">';
if ($thumb) {
echo '<img class="fit-cover radius8 aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
} else {
echo '<span class="hot-posts-graphic__empty"></span>';
}
echo '<div class="absolute linear-mask"></div>';
echo '<div class="abs-center left-bottom box-body">';
echo '<div class="mb6"><span class="badg b-theme badg-sm"><i class="fa ' . esc_attr($metric_icon) . '" aria-hidden="true"></i> ' . esc_html($metric_txt . $metric_label) . '</span></div>';
echo '<div class="hot-posts-graphic__title">' . esc_html(wp_html_excerpt($title, 48, '…')) . '</div>';
echo '</div></div></a>';
echo $top_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
continue;
}
$rank_class = $index <= 3 ? ' rank-top' : '';
$thumb = $show_thumb ? get_the_post_thumbnail_url($pid, 'thumbnail') : '';
?>
<div class="hot-post-item flex mt15 relative hover-zoom-img ajax-item <?php echo esc_attr($rank_class); ?>">
<?php echo $top_badge; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
<?php if ($show_thumb) : ?>
<a class="hot-post-item__thumb graphic<?php echo $thumb ? '' : ' is-empty'; ?>" href="<?php echo esc_url($permalink); ?>"<?php echo $tgt; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
<?php if ($thumb) : ?><img class="fit-cover radius8 aksrt-lazy lazyload" data-src="<?php echo esc_url($thumb); ?>" src="<?php echo esc_url($placeholder); ?>" alt="" loading="lazy" /><?php endif; ?>
</a>
<?php endif; ?>
<div class="hot-post-item__info term-title ml10 flex xx flex1 jsb">
<div class="text-ellipsis-2"><a href="<?php echo esc_url($permalink); ?>" class="hot-post-item__title"<?php echo $tgt; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>><?php echo esc_html($title); ?></a></div>
<div class="px12 muted-3-color text-ellipsis flex jsb hot-post-item__meta">
<span class="hot-post-item__time"><i class="fa fa-clock-o mr3" aria-hidden="true"></i><?php echo esc_html($time_ago); ?></span>
<span><i class="fa <?php echo esc_attr($metric_icon); ?>" aria-hidden="true"></i> <?php echo esc_html((string) $metric_txt); ?></span>
</div>
</div>
</div>
<?php
}
wp_reset_postdata();
return (string) ob_get_clean();
}
public static function query_payload(array $instance, int $page = 1): array {
$page = max(1, $page);
$count = max(1, min(20, (int) ($instance['count'] ?? 5)));
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$q = new \WP_Query(self::build_query_args($instance, $page));
$items = self::render_items_html($q, $instance, ($page - 1) * $count);
$html = $items !== '' ? '<div class="widget-hot-posts widget-hot-posts--graphic zib-widget">' . $items . '</div>' : '';
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('暂无热门文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无热门文章', 'aksrt') . '</p>';
}
$max_pages = max(1, (int) $q->max_num_pages);
$total = (int) $q->found_posts;
$has_more = $page < $max_pages;
$base_qs = array_filter([
'count' => $count,
'order_by' => sanitize_key((string) ($instance['order_by'] ?? 'views')),
'days' => max(0, (int) ($instance['days'] ?? 30)),
'show_thumb' => !empty($instance['show_thumb']) ? 1 : 0,
'target_blank'=> !empty($instance['target_blank']) ? 1 : 0,
'paginate' => $paginate,
], static function ($v) {
return $v !== '' && $v !== null;
});
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/hot-posts'));
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg(['page' => $page + 1], $rest_base);
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
} elseif ($paginate === 'number' && $max_pages > 1) {
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
} else {
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
return compact('html', 'paginate_html', 'has_more', 'page', 'max_pages', 'total');
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', $instance['title'] ?? __('热门文章', 'aksrt'), $instance, $this->id_base);
if ($title) {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$instance['paginate'] = sanitize_key((string) ($instance['paginate'] ?? 'ajax'));
$payload = self::query_payload($instance, 1);
$rest = rest_url('aksrt/v1/widgets/hot-posts');
echo '<div class="ajaxpager" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '">';
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$title = $instance['title'] ?? __('热门文章', 'aksrt');
$count = (int) ($instance['count'] ?? 5);
$order_by = $instance['order_by'] ?? 'views';
$days = (int) ($instance['days'] ?? 30);
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr((string) $title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="20" value="<?php echo (int) $count; ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('order_by')); ?>"><?php esc_html_e('排序方式:', 'aksrt'); ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('order_by')); ?>" name="<?php echo esc_attr($this->get_field_name('order_by')); ?>">
<option value="views" <?php selected($order_by, 'views'); ?>><?php esc_html_e('浏览量', 'aksrt'); ?></option>
<option value="comments" <?php selected($order_by, 'comments'); ?>><?php esc_html_e('评论数', 'aksrt'); ?></option>
<option value="likes" <?php selected($order_by, 'likes'); ?>><?php esc_html_e('点赞数', 'aksrt'); ?></option>
<option value="favorites" <?php selected($order_by, 'favorites'); ?>><?php esc_html_e('收藏数', 'aksrt'); ?></option>
<option value="downloads" <?php selected($order_by, 'downloads'); ?>><?php esc_html_e('下载数', 'aksrt'); ?></option>
</select>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('days')); ?>"><?php esc_html_e('时间范围(天,0=不限):', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('days')); ?>" name="<?php echo esc_attr($this->get_field_name('days')); ?>" type="number" min="0" max="365" value="<?php echo (int) $days; ?>">
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示缩略图', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="ajax" <?php selected(($instance['paginate'] ?? 'ajax'), 'ajax'); ?>><?php esc_html_e('AJAX', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
<option value="none" <?php selected(($instance['paginate'] ?? ''), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field($new_instance['title'] ?? ''),
'count' => absint($new_instance['count'] ?? 5),
'order_by' => in_array($new_instance['order_by'] ?? '', ['views', 'comments', 'likes', 'favorites', 'downloads'], true) ? $new_instance['order_by'] : 'views',
'days' => absint($new_instance['days'] ?? 30),
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
'paginate' => in_array(sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')), ['none', 'ajax', 'number'], true) ? sanitize_key((string) ($new_instance['paginate'] ?? 'ajax')) : 'ajax',
];
}
}
-206
View File
@@ -1,206 +0,0 @@
<?php
/**
* Vertical icon cards grid (zibll zib_widget_ui_icon_card → aksrt_icon_card).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class IconCardsWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_icon_cards', esc_html__('AKSRT 图标卡片', 'aksrt'), [
'description' => esc_html__('图标在上、文案在下的竖版图标卡片网格。', 'aksrt'),
'classname' => 'widget_aksrt_icon_cards',
]);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$cards = $this->normalize($instance['cards'] ?? []);
if (!$cards) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一张图标卡', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一张图标卡', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$pc_row = max(1, min(6, (int) ($instance['columns'] ?? $instance['pc_row'] ?? 2)));
$m_row = max(1, min(4, (int) ($instance['m_row'] ?? 2)));
$go_link = !empty($instance['go_link']);
$show_bg = !isset($instance['show_widget_bg']) || !empty($instance['show_widget_bg']);
$icon_size = max(0, min(64, (int) ($instance['icon_size'] ?? $instance['size'] ?? 0)));
$icon_radius = !empty($instance['icon_radius']);
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$is_row = count($cards) > 1;
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
echo $show_bg ? '<div class="zib-widget nobottom notop">' : '<div class="clear">';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($cards as $card) {
$url = (string) ($card['url'] ?? '');
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
$card_args = [
'class' => $show_bg ? 'box-body nopw-sm' : 'mb20',
'icon' => (string) ($card['icon'] ?? ''),
'icon_image' => (string) ($card['icon_image'] ?? ''),
'icon_size' => $icon_size > 0 ? (16 + $icon_size) : 0,
'icon_radius' => $icon_radius || !empty($card['icon_radius']),
'icon_color' => (string) ($card['icon_color'] ?? ''),
'icon_class' => (string) ($card['icon_class'] ?? ''),
'title' => (string) ($card['title'] ?? ''),
'desc' => (string) ($card['desc'] ?? ''),
'link' => [
'url' => $url,
'target' => (string) ($card['target'] ?? ''),
],
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if (function_exists('aksrt_icon_card')) {
echo aksrt_icon_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array<string,string>>
*/
private function normalize($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $card) {
if (!is_array($card)) {
continue;
}
$title = sanitize_text_field((string) ($card['title'] ?? ''));
$icon = sanitize_text_field((string) ($card['icon'] ?? ''));
$icon_image = esc_url_raw(trim((string) ($card['icon_image'] ?? $card['customize_icon'] ?? '')));
if ($title === '' && $icon === '' && $icon_image === '') {
continue;
}
$url = esc_url_raw(trim((string) ($card['link'] ?? $card['url'] ?? '')));
if (is_array($card['link'] ?? null)) {
$url = esc_url_raw(trim((string) ($card['link']['url'] ?? '')));
$target = sanitize_text_field((string) ($card['link']['target'] ?? ''));
} else {
$target = sanitize_text_field((string) ($card['target'] ?? ''));
}
$out[] = [
'title' => $title,
'desc' => sanitize_text_field((string) ($card['description'] ?? $card['desc'] ?? '')),
'icon' => $icon,
'icon_image' => $icon_image,
'icon_color' => sanitize_html_class((string) ($card['icon_color'] ?? '')),
'icon_class' => sanitize_html_class((string) ($card['icon_class'] ?? '')),
'url' => $url,
'target' => $target,
];
if (count($out) >= 12) {
break;
}
}
return $out;
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$cards = $this->normalize($instance['cards'] ?? []);
while (count($cards) < 4) {
$cards[] = ['title' => '', 'icon' => '', 'url' => '', 'desc' => ''];
}
?>
<p>
<label><?php esc_html_e('标题(可选):', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('PC 列数:', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 2))); ?>">
</label>
<label><?php esc_html_e('移动列数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 2))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_widget_bg')); ?>" value="1" <?php checked(!isset($instance['show_widget_bg']) || !empty($instance['show_widget_bg'])); ?>> <?php esc_html_e('卡片背景', 'aksrt'); ?></label>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('icon_radius')); ?>" value="1" <?php checked(!empty($instance['icon_radius'])); ?>> <?php esc_html_e('圆形底图标', 'aksrt'); ?></label>
</p>
<hr>
<?php foreach ($cards as $i => $card) : ?>
<div style="border:1px solid #ddd;padding:6px;margin-bottom:6px;border-radius:4px;">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][title]")); ?>" value="<?php echo esc_attr((string) ($card['title'] ?? '')); ?>" placeholder="<?php esc_attr_e('标题', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][icon]")); ?>" value="<?php echo esc_attr((string) ($card['icon'] ?? '')); ?>" placeholder="<?php esc_attr_e('图标类名', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][desc]")); ?>" value="<?php echo esc_attr((string) ($card['desc'] ?? '')); ?>" placeholder="<?php esc_attr_e('描述', 'aksrt'); ?>">
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name("cards[{$i}][url]")); ?>" value="<?php echo esc_attr((string) ($card['url'] ?? '')); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
</div>
<?php endforeach;
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'columns' => max(1, min(6, (int) ($new_instance['columns'] ?? 2))),
'm_row' => max(1, min(4, (int) ($new_instance['m_row'] ?? 2))),
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
'icon_radius' => !empty($new_instance['icon_radius']) ? 1 : 0,
'cards' => $this->normalize($new_instance['cards'] ?? []),
]; return $business;
}
}
-18
View File
@@ -1,18 +0,0 @@
<?php
/** Structured marketing widgets. */
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
abstract class MarketingWidgetBase extends \WP_Widget {
protected string $mode = 'cards';
public function __construct(string $id, string $name, string $mode) { $this->mode=$mode; parent::__construct($id, esc_html__('AKSRT '.$name,'aksrt'), ['description'=>esc_html__('结构化营销展示模块','aksrt')]); }
public function form($instance): void { $title=sanitize_text_field((string)($instance['title']??'')); $items=(array)($instance['items']??[]); if(!$items && !empty($instance['items_raw'])) $items=array_filter(array_map('trim',preg_split('/\r?\n/',(string)$instance['items_raw']))); $raw=implode("\n",array_map(static fn($row)=>is_array($row)?implode('|',array_map('strval',$row)):(string)$row,$items)); echo '<p><label>'.esc_html__('标题','aksrt').'</label><input class="widefat" name="'.esc_attr($this->get_field_name('title')).'" value="'.esc_attr($title).'"></p><p><label>'.esc_html__('条目(每行:标题|描述|链接)','aksrt').'</label><textarea class="widefat" rows="6" name="'.esc_attr($this->get_field_name('items_raw')).'">'.esc_textarea($raw).'</textarea></p>'; }
public function update($new_instance,$old_instance): array { $rows=[]; foreach(preg_split('/\r?\n/',(string)($new_instance['items_raw']??'')) as $line){$parts=array_map('trim',explode('|',$line,3)); if(trim(implode('',$parts))==='')continue; $rows[]=['title'=>sanitize_text_field($parts[0]??''),'desc'=>sanitize_text_field($parts[1]??''),'url'=>esc_url_raw($parts[2]??'')];} return ['title'=>sanitize_text_field($new_instance['title']??''),'items'=>$rows]; }
public function widget($args,$instance): void { echo $args['before_widget']; $title=apply_filters('widget_title',$instance['title']??'',$instance,$this->id_base); if($title) echo $args['before_title'].esc_html($title).$args['after_title']; $items=(array)($instance['items']??[]); echo '<div class="aksrt-marketing-block aksrt-marketing-block--'.esc_attr($this->mode).'">'; if(!$items){echo '<p class="widget-empty">'.esc_html__('暂无内容','aksrt').'</p>';} else { foreach($items as $item){$title=sanitize_text_field((string)($item['title']??''));$desc=sanitize_text_field((string)($item['desc']??''));$url=esc_url((string)($item['url']??''));$tag=$url?'<a href="'.$url.'">':'<div>'; echo '<article class="aksrt-marketing-card">'.$tag.'<strong>'.esc_html($title).'</strong>'.($desc!==''?'<p>'.esc_html($desc).'</p>':'').($url?'</a>':'</div>').'</article>'; }} echo '</div>'.$args['after_widget']; }
}
final class PricingPlansWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_pricing_plans','定价计划','pricing');} }
final class TestimonialsWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_testimonials','评价轮播','testimonials');} }
final class FaqWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_faq','FAQ 常见问题','faq');} }
final class StatsWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_stats','数据统计','stats');} }
final class HighlightsWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_highlights','亮点卡片','highlights');} }
final class PartnersWidget extends MarketingWidgetBase { public function __construct(){parent::__construct('aksrt_partners','合作伙伴滚动','partners');} }
-206
View File
@@ -1,206 +0,0 @@
<?php
/**
* Scrolling notice bar (zibll widget_ui_notice / zib_notice → vertical new-swiper).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class NoticeWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_notice', esc_html__('AKSRT 滚动公告', 'aksrt'), [
'description' => esc_html__('多条公告垂直滚动(共享 Swiper 栈)。', 'aksrt'),
'classname' => 'widget_aksrt_notice',
]);
}
public function widget($args, $instance): void {
$items = $this->normalize_items($instance);
if (!$items) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一条公告', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一条公告', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$color = sanitize_html_class((string) ($instance['color'] ?? 'c-blue'));
$blank = !empty($instance['blank']);
$radius = !empty($instance['radius']);
$interval = max(2, min(15, (int) ($instance['interval'] ?? 4)));
$align = sanitize_html_class((string) ($instance['alignment'] ?? ''));
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$wrap_cls = 'aksrt-scroll-notice swiper-bulletin color-' . $color;
if ($radius) {
$wrap_cls .= ' is-radius radius';
}
if ($align !== '') {
$wrap_cls .= ' ' . $align;
}
echo '<div class="' . esc_attr($wrap_cls) . '">';
echo '<div class="aksrt-scroll-notice__icon bulletin-icon"><i class="fas fa-bullhorn" aria-hidden="true"></i></div>';
// Prefer shared vertical new-swiper (zib_notice parity) via slider.js Type C.
echo '<div class="new-swiper aksrt-notice-swiper" data-direction="vertical" data-loop data-autoplay data-interval="' . esc_attr((string) ($interval * 1000)) . '">';
echo '<div class="swiper-wrapper">';
foreach ($items as $item) {
$inner = '';
if ($item['icon'] !== '') {
$icon = $item['icon'];
if (strpos($icon, 'fa-') === 0 && strpos($icon, ' ') === false) {
$icon = 'fa ' . $icon;
}
$inner .= '<div class="relative bulletin-icon mr6"><i class="abs-center ' . esc_attr($icon) . '" aria-hidden="true"></i></div>';
}
$inner .= esc_html($item['title']);
echo '<div class="swiper-slide notice-slide">';
if ($item['href'] !== '') {
echo '<a class="text-ellipsis"' .
($blank ? ' target="_blank" rel="noopener noreferrer"' : '') .
' href="' . esc_url($item['href']) . '">' . $inner . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '<span class="text-ellipsis">' . $inner . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
}
echo '</div></div>';
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
* @return array<int, array{title:string,icon:string,href:string}>
*/
private function normalize_items(array $instance): array {
$raw = $instance['items'] ?? $instance['img_ids'] ?? null;
if ($raw === null && !empty($instance['content'])) {
return [[
'title' => wp_strip_all_tags((string) $instance['content']),
'icon' => sanitize_text_field((string) ($instance['icon'] ?? 'fas fa-bullhorn')),
'href' => '',
]];
}
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$title = sanitize_text_field((string) ($row['title'] ?? $row['content'] ?? ''));
if ($title === '') {
continue;
}
$out[] = [
'title' => $title,
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
'href' => esc_url_raw(trim((string) ($row['href'] ?? $row['url'] ?? $row['link'] ?? ''))),
];
if (count($out) >= 12) {
break;
}
}
return $out;
}
public function form($instance): void {
$items = $this->normalize_items($instance);
if (!$items) {
$items = [
['title' => '', 'icon' => 'fas fa-bullhorn', 'href' => ''],
['title' => '', 'icon' => 'fas fa-info-circle', 'href' => ''],
];
}
$json = wp_json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$colors = [
'c-blue' => __('透明蓝', 'aksrt'),
'c-green' => __('透明绿', 'aksrt'),
'c-yellow' => __('透明黄', 'aksrt'),
'c-red' => __('透明粉红', 'aksrt'),
'c-purple' => __('透明紫', 'aksrt'),
'c-blue-2' => __('透明深蓝', 'aksrt'),
'c-green-2' => __('透明墨绿', 'aksrt'),
'c-yellow-2' => __('透明橘黄', 'aksrt'),
'c-red-2' => __('透明红', 'aksrt'),
'c-purple-2' => __('透明深紫', 'aksrt'),
'b-theme' => __('主题色', 'aksrt'),
'b-red' => __('红色', 'aksrt'),
'b-yellow' => __('黄色', 'aksrt'),
'b-blue' => __('蓝色', 'aksrt'),
'b-green' => __('绿色', 'aksrt'),
'b-purple' => __('紫色', 'aksrt'),
];
?>
<p>
<label><?php esc_html_e('标题(可选)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('公告条目 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('items')); ?>" placeholder='[{"title":"公告内容","icon":"fas fa-bullhorn","href":""}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('切换间隔(秒)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="2" max="15" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['interval'] ?? 4))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('主题色彩', 'aksrt'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('color')); ?>">
<?php foreach ($colors as $k => $label) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['color'] ?? 'c-blue'), $k); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('对齐', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('alignment')); ?>">
<option value="" <?php selected(($instance['alignment'] ?? ''), ''); ?>><?php esc_html_e('靠左', 'aksrt'); ?></option>
<option value="text-center" <?php selected(($instance['alignment'] ?? ''), 'text-center'); ?>><?php esc_html_e('居中', 'aksrt'); ?></option>
<option value="text-right" <?php selected(($instance['alignment'] ?? ''), 'text-right'); ?>><?php esc_html_e('靠右', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('链接新窗口', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('radius')); ?>" value="1" <?php checked(!empty($instance['radius'])); ?>> <?php esc_html_e('两端圆形', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'items' => $this->normalize_items(['items' => $new_instance['items'] ?? []]),
'interval' => max(2, min(15, (int) ($new_instance['interval'] ?? 4))),
'color' => sanitize_html_class((string) ($new_instance['color'] ?? 'c-blue')),
'alignment' => sanitize_html_class((string) ($new_instance['alignment'] ?? '')),
'blank' => !empty($new_instance['blank']) ? 1 : 0,
'radius' => !empty($new_instance['radius']) ? 1 : 0,
];
}
}
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-214
View File
@@ -1,214 +0,0 @@
<?php
/**
* Recent comments mini list (zibll widget_ui_new_comment → zib_widget_comments).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class RecentCommentsWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_recent_comments', esc_html__('AKSRT 最新评论', 'aksrt'), [
'description' => esc_html__('显示最新评论列表(posts-mini + 身份/点赞)。', 'aksrt'),
]);
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$count = max(1, min(50, (int) ($instance['count'] ?? $instance['limit'] ?? 5)));
$show_avatar = !isset($instance['show_avatar']) || !empty($instance['show_avatar']);
// zibll: outpost = post__not_in (multi-ID), outer = author__not_in (multi-ID).
// Also keep optional keyword filter via outer_keywords for legacy installs.
$outpost_raw = (string) ($instance['outpost'] ?? '');
$outer_raw = (string) ($instance['outer'] ?? '');
$keywords = sanitize_text_field((string) ($instance['outer_keywords'] ?? ''));
$post_not_in = array_values(array_filter(array_map('intval', preg_split('/[,,\s]+/', $outpost_raw) ?: [])));
// Heuristic: if outer looks numeric/id-list → author__not_in; else treat as keyword filter (legacy).
$author_not_in = [];
$legacy_keys = [];
if ($outer_raw !== '') {
$parts = array_filter(array_map('trim', preg_split('/[,,\s]+/', $outer_raw) ?: []));
$all_numeric = $parts && array_reduce($parts, static function ($ok, $p) {
return $ok && ctype_digit((string) $p);
}, true);
if ($all_numeric) {
$author_not_in = array_values(array_filter(array_map('intval', $parts)));
} else {
$legacy_keys = $parts;
}
}
if ($keywords !== '') {
$legacy_keys = array_merge($legacy_keys, array_filter(array_map('trim', preg_split('/[,,]+/', $keywords) ?: [])));
}
$query = [
'number' => $count * 2, // over-fetch when filtering keywords.
'status' => 'approve',
'post_status' => 'publish',
'post_type' => ['post'],
'orderby' => 'comment_date_gmt',
'order' => 'DESC',
];
if ($post_not_in) {
$query['post__not_in'] = $post_not_in;
}
if ($author_not_in) {
$query['author__not_in'] = $author_not_in;
}
$comments = get_comments($query);
if ($legacy_keys && $comments) {
$comments = array_values(array_filter($comments, static function ($c) use ($legacy_keys) {
$body = (string) $c->comment_content;
foreach ($legacy_keys as $k) {
if ($k !== '' && str_contains($body, $k)) {
return false;
}
}
return true;
}));
}
$comments = array_slice($comments ?: [], 0, $count);
if (!$comments) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无评论', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无评论', 'aksrt') . '</p>';
}
ob_start();
// zibll: .box-body.comment-mini-lists.zib-widget + .posts-mini rows.
echo '<div class="box-body comment-mini-lists zib-widget ajax-item widget-recent-comments">';
foreach ($comments as $comment) {
$cid = (int) $comment->comment_ID;
$user_id = (int) $comment->user_id;
$link = get_comment_link($comment);
$time = human_time_diff(strtotime((string) $comment->comment_date), current_time('timestamp'));
// Content: strip imgs/code like zibll noimg filter.
$cont = get_comment_text($cid);
$cont = convert_smilies($cont);
$cont = preg_replace('/\<img(.*?)\>/i', '[' . __('图片', 'aksrt') . ']', $cont);
$cont = preg_replace('/\[code]([\s\S]*?)\[\/code]/i', '[' . __('代码', 'aksrt') . ']', (string) $cont);
$cont = wp_strip_all_tags((string) $cont);
$cont = wp_trim_words($cont, 40, '…');
// Name with identity.
if ($user_id > 0 && function_exists('aksrt_get_user_name')) {
$user_name = aksrt_get_user_name([
'id' => $user_id,
'class' => 'inflex ac relative-h',
'follow' => false,
'medal' => true,
'level' => true,
'auth' => true,
]);
} elseif ($user_id > 0) {
$home = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id);
$user_name = '<a href="' . esc_url($home) . '">' . esc_html(get_comment_author($comment)) . '</a>';
} else {
$user_name = esc_html(get_comment_author($comment));
}
$avatar = '';
if ($show_avatar) {
$avatar_url = $user_id > 0 && function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($user_id, ['size' => 42])
: get_avatar_url($comment, ['size' => 42]);
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
$avatar = '<div class="avatar-img"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url($ph) . '" alt="" width="36" height="36" loading="lazy" /></div>';
}
$like = function_exists('aksrt_get_comment_like_html')
? aksrt_get_comment_like_html($comment, true)
: '';
echo '<div class="posts-mini">';
echo $avatar; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="posts-mini-con em09 ml10 flex xx jsb">';
echo '<p class="flex jsb">';
echo '<span class="flex1 flex">';
echo $user_name; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<span class="flex0 icon-spot muted-3-color" title="' . esc_attr((string) $comment->comment_date) . '">' . esc_html($time) . '</span>';
echo '</span>';
if ($like !== '') {
echo '<span class="ml10 flex0">' . $like . '</span>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</p>';
echo '<a class="muted-color text-ellipsis-5" href="' . esc_url($link) . '">' . esc_html($cont) . '</a>';
echo '</div></div>';
}
echo '</div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? __('最新评论', 'aksrt')), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? __('最新评论', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="50" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 5))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_avatar')); ?>" value="1" <?php checked(!isset($instance['show_avatar']) || !empty($instance['show_avatar'])); ?>> <?php esc_html_e('显示头像', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('排除文章 ID(逗号)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('outpost')); ?>" value="<?php echo esc_attr((string) ($instance['outpost'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排除作者 ID(逗号)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('outer')); ?>" value="<?php echo esc_attr((string) ($instance['outer'] ?? '')); ?>">
</label>
<small><?php esc_html_e('填数字 ID 时按作者排除;填文字时按内容关键词排除(兼容旧配置)。', 'aksrt'); ?></small>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(50, (int) ($new_instance['count'] ?? 5))),
'show_avatar' => !empty($new_instance['show_avatar']) ? 1 : 0,
'outpost' => sanitize_text_field((string) ($new_instance['outpost'] ?? '')),
'outer' => sanitize_text_field((string) ($new_instance['outer'] ?? '')),
'outer_keywords' => sanitize_text_field((string) ($new_instance['outer_keywords'] ?? '')),
];
}
}
-176
View File
@@ -1,176 +0,0 @@
<?php
/**
* Search box with hot keywords.
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class SearchWidget extends \WP_Widget {
public function __construct() {
parent::__construct('aksrt_search', esc_html__('AKSRT 搜索', 'aksrt'), [
'description' => esc_html__('搜索框:热门关键词。', 'aksrt'),
'classname' => 'widget_aksrt_search',
]);
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$placeholder = sanitize_text_field((string) ($instance['placeholder'] ?? __('开启精彩搜索', 'aksrt')));
if ($placeholder === '' && function_exists('aksrt_get_search_placeholder')) {
$placeholder = aksrt_get_search_placeholder();
}
$show_kw = !empty($instance['show_keywords']);
$kw_title = sanitize_text_field((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt')));
$keyword_limit = max(1, min(50, (int) ($instance['keyword_limit'] ?? 12)));
$search_type = function_exists('aksrt_get_default_search_type')
? aksrt_get_default_search_type()
: 'post';
$keywords = [];
if ($show_kw) {
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'hot_search_keywords')) {
$keywords = \AKSRT\Settings\Settings::hot_search_keywords();
} elseif (function_exists('aksrt_get_search_hot_keywords')) {
$keywords = aksrt_get_search_hot_keywords();
} elseif (function_exists('aksrt_get_setting')) {
$raw = (string) aksrt_get_setting('hot_search_keywords', '');
$keywords = array_values(array_filter(array_map('trim', preg_split('/[\r\n,,]+/', $raw) ?: [])));
}
$custom = trim((string) ($instance['keywords'] ?? ''));
if ($custom !== '') {
$keywords = array_values(array_filter(array_map('trim', preg_split('/[\r\n,,]+/', $custom) ?: [])));
}
$keywords = array_values(array_filter(array_map(static function ($keyword): string {
return is_array($keyword)
? trim((string) ($keyword['keyword'] ?? $keyword['text'] ?? ''))
: trim((string) $keyword);
}, is_array($keywords) ? $keywords : [])));
$keywords = array_values(array_unique($keywords));
}
$keyword_pool = $keywords;
if (count($keywords) > $keyword_limit) {
shuffle($keywords);
$keywords = array_slice($keywords, 0, $keyword_limit);
}
$uid = 'aksrt-sw-' . esc_attr($this->id);
?>
<div class="widget-search-box aksrt-search-widget search-input" id="<?php echo $uid; ?>">
<form
role="search"
method="get"
class="aksrt-search-widget__form search-form"
action="<?php echo esc_url(home_url('/')); ?>"
data-aksrt-search-form="widget"
data-default-search-type="<?php echo esc_attr($search_type); ?>"
>
<div class="aksrt-search-widget__line line-form">
<div class="aksrt-search-widget__field search-input-text">
<label class="screen-reader-text" for="<?php echo $uid; ?>-s"><?php esc_html_e('搜索关键词', 'aksrt'); ?></label>
<input
id="<?php echo $uid; ?>-s"
type="search"
name="s"
class="widget-search-box__input line-form-input"
placeholder="<?php echo esc_attr($placeholder); ?>"
value="<?php echo esc_attr(get_search_query()); ?>"
autocomplete="off"
>
<button type="submit" class="widget-search-box__btn" aria-label="<?php esc_attr_e('搜索', 'aksrt'); ?>">
<i class="fas fa-search" aria-hidden="true"></i>
</button>
</div>
</div>
<input type="hidden" name="aksrt_search_type" value="<?php echo esc_attr($search_type); ?>">
</form>
<?php if ($keywords) : ?>
<div class="aksrt-search-widget__keywords">
<div class="aksrt-search-widget__kw-head">
<?php if ($kw_title !== '') : ?>
<div class="aksrt-search-widget__kw-title"><?php echo esc_html($kw_title); ?></div>
<?php endif; ?>
<button type="button" class="aksrt-search-widget__kw-refresh" data-aksrt-search-keyword-refresh aria-label="<?php esc_attr_e('刷新热门搜索', 'aksrt'); ?>">
<i class="fas fa-sync-alt" aria-hidden="true"></i>
</button>
</div>
<div
class="aksrt-search-widget__kw-list"
data-aksrt-search-keywords
data-keywords="<?php echo esc_attr((string) wp_json_encode($keyword_pool)); ?>"
data-keyword-limit="<?php echo esc_attr((string) $keyword_limit); ?>"
data-search-type="<?php echo esc_attr($search_type); ?>"
data-search-url="<?php echo esc_url(home_url('/')); ?>"
>
<?php foreach ($keywords as $kw) :
$url = function_exists('aksrt_build_search_url')
? aksrt_build_search_url($kw, ['aksrt_search_type' => $search_type])
: add_query_arg(['s' => $kw, 'aksrt_search_type' => $search_type], home_url('/'));
?>
<a class="aksrt-search-widget__kw" href="<?php echo esc_url($url); ?>"><?php echo esc_html($kw); ?></a>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<?php
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
?>
<p>
<label><?php esc_html_e('标题(可选)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('占位文本', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('placeholder')); ?>" value="<?php echo esc_attr((string) ($instance['placeholder'] ?? __('开启精彩搜索', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_keywords')); ?>" value="1" <?php checked(!empty($instance['show_keywords'])); ?>> <?php esc_html_e('显示热门关键词', 'aksrt'); ?></label>
</p>
<p>
<label><?php esc_html_e('关键词标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('keywords_title')); ?>" value="<?php echo esc_attr((string) ($instance['keywords_title'] ?? __('热门搜索', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('自定义关键词(逗号/换行;空=后台配置)', 'aksrt'); ?>
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('keywords')); ?>"><?php echo esc_textarea((string) ($instance['keywords'] ?? '')); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('关键词最多显示数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="50" name="<?php echo esc_attr($this->get_field_name('keyword_limit')); ?>" value="<?php echo esc_attr((string) max(1, min(50, (int) ($instance['keyword_limit'] ?? 12)))); ?>">
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'placeholder' => sanitize_text_field((string) ($new_instance['placeholder'] ?? '')),
'show_keywords' => !empty($new_instance['show_keywords']) ? 1 : 0,
'keywords_title' => sanitize_text_field((string) ($new_instance['keywords_title'] ?? '')),
'keywords' => sanitize_textarea_field((string) ($new_instance['keywords'] ?? '')),
'keyword_limit' => max(1, min(50, (int) ($new_instance['keyword_limit'] ?? 12))),
];
}
}
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-391
View File
@@ -1,391 +0,0 @@
<?php
/**
* Multi-tab posts (zibll zib_widget_ui_tab_post + lazy REST).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class TabPostsWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_tab_posts',
esc_html__('AKSRT 多栏目文章', 'aksrt'),
[
'description' => esc_html__('Tab 切换显示不同分类/排序的文章(懒加载)。', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
/**
* @param mixed $tabs
* @return list<array{title:string,category_id:int,orderby:string,count:int,enabled:bool}>
*/
public static function normalize_tabs_static($tabs): array {
if (is_string($tabs) && $tabs !== '') {
$legacy_keys = array_map('trim', explode(',', $tabs));
$legacy_defs = [
'latest' => ['title' => '最新', 'orderby' => 'date'],
'comments' => ['title' => '热评', 'orderby' => 'comments'],
'random' => ['title' => '随机', 'orderby' => 'rand'],
'views' => ['title' => '热门', 'orderby' => 'views'],
];
$result = [];
foreach ($legacy_keys as $key) {
if (isset($legacy_defs[$key])) {
$result[] = [
'title' => __($legacy_defs[$key]['title'], 'aksrt'),
'category_id' => 0,
'orderby' => $legacy_defs[$key]['orderby'],
'count' => 0,
'enabled' => true,
];
} elseif (is_numeric($key) && absint($key) > 0) {
$term = get_term(absint($key), 'category');
$result[] = [
'title' => ($term && !is_wp_error($term)) ? $term->name : '',
'category_id' => absint($key),
'orderby' => 'date',
'count' => 0,
'enabled' => true,
];
}
}
return $result;
}
if (is_array($tabs)) {
$allowed = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
$result = [];
foreach ($tabs as $tab) {
if (!is_array($tab)) {
continue;
}
$orderby = sanitize_key($tab['orderby'] ?? 'date');
if (!in_array($orderby, $allowed, true)) {
$orderby = 'date';
}
$result[] = [
'title' => sanitize_text_field((string) ($tab['title'] ?? '')),
'category_id' => absint($tab['category_id'] ?? 0),
'orderby' => $orderby,
'count' => max(0, min(20, (int) ($tab['count'] ?? 0))),
'enabled' => !empty($tab['enabled']),
];
}
return $result;
}
return [];
}
private function normalize_tabs($tabs): array {
return self::normalize_tabs_static($tabs);
}
/**
* Build query for a tab → reuse main posts payload renderer.
*
* @param array $tab
* @param int $default_count
* @return array
*/
public static function tab_to_main_instance(array $tab, int $default_count, array $opts = []): array {
$count = !empty($tab['count']) ? (int) $tab['count'] : $default_count;
$style = sanitize_key((string) ($opts['style'] ?? $tab['style'] ?? 'mini'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'mini';
}
$paginate = sanitize_key((string) ($opts['paginate'] ?? $tab['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
return [
'count' => max(1, min(30, $count)),
'orderby' => sanitize_key((string) ($tab['orderby'] ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($tab['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => absint($tab['category_id'] ?? 0),
'limit_day' => max(0, (int) ($tab['limit_day'] ?? 0)),
'pay_type' => sanitize_key((string) ($tab['pay_type'] ?? '')),
'style' => $style,
'show_thumb' => !isset($opts['show_thumb']) ? 1 : (int) $opts['show_thumb'],
'show_meta' => !isset($opts['show_meta']) ? 1 : (int) $opts['show_meta'],
'show_number' => !empty($opts['show_number']) ? 1 : 0,
'paginate' => $paginate,
];
}
/**
* REST helper: render one tab list HTML.
*
* @param array<string,mixed> $opts
*/
public static function render_tab_html(array $tab, int $default_count, array $opts = []): string {
$payload = self::render_tab_payload($tab, $default_count, $opts, 1);
return (string) ($payload['html'] ?? '');
}
/**
* Full tab payload (html + pager) for REST/ajax.
*
* @param array<string,mixed> $opts
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
*/
public static function render_tab_payload(array $tab, int $default_count, array $opts = [], int $page = 1): array {
$inst = self::tab_to_main_instance($tab, $default_count, $opts);
if (class_exists('\AKSRT\Widgets\WidgetMainPosts')) {
return WidgetMainPosts::query_payload($inst, $page);
}
return [
'html' => '',
'paginate_html' => '',
'has_more' => false,
'page' => 1,
'max_pages' => 1,
'total' => 0,
];
}
/** Skeleton rows for lazy tab panels. */
public static function skeleton_html(int $count = 5, string $style = 'mini'): string {
$count = max(1, min(12, $count));
ob_start();
echo '<div class="widget-tab-posts__skeleton posts-mini-lists style-' . esc_attr($style) . '">';
for ($i = 0; $i < $count; $i++) {
echo '<div class="posts-mini placeholder-row">';
echo '<div class="placeholder radius thumb-placeholder"></div>';
echo '<div class="flex1"><div class="placeholder k1"></div><div class="placeholder s1 mt6"></div></div>';
echo '</div>';
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
$count = absint($instance['count'] ?? 5);
$style = sanitize_key((string) ($instance['style'] ?? 'mini'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'mini';
}
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
$tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random');
$tabs = array_values(array_filter($tabs, static fn($t) => !empty($t['enabled'])));
if (!$tabs) {
if (current_user_can('manage_options')) {
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请至少启用一个 Tab', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请至少启用一个 Tab', 'aksrt') . '</p>';
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$widget_id = $this->id;
$rest = rest_url('aksrt/v1/widgets/tab-posts');
$use_shared = function_exists('aksrt_render_mini_list');
?>
<div class="widget-tab-posts" id="<?php echo esc_attr($widget_id); ?>-tabs"
data-aksrt-tab-posts
data-rest="<?php echo esc_url($rest); ?>"
data-count="<?php echo esc_attr((string) $count); ?>"
data-style="<?php echo esc_attr($style); ?>"
data-paginate="<?php echo esc_attr($paginate); ?>">
<ul class="widget-tab-posts__nav" role="tablist">
<?php $first = true; foreach ($tabs as $i => $tab) :
$tab_key = 'tab-' . $i;
$tab_title = $tab['title'] !== '' ? $tab['title'] : sprintf(__('Tab %d', 'aksrt'), $i + 1);
?>
<li role="presentation">
<button type="button" role="tab"
class="widget-tab-posts__tab<?php echo $first ? ' active' : ''; ?>"
data-tab="<?php echo esc_attr($tab_key); ?>"
data-index="<?php echo (int) $i; ?>"
data-orderby="<?php echo esc_attr($tab['orderby']); ?>"
data-cat="<?php echo (int) $tab['category_id']; ?>"
data-tab-count="<?php echo (int) $tab['count']; ?>"
data-limit-day="<?php echo esc_attr((string) ((int) ($tab['limit_day'] ?? 0))); ?>"
data-pay-type="<?php echo esc_attr((string) ($tab['pay_type'] ?? '')); ?>"
aria-selected="<?php echo $first ? 'true' : 'false'; ?>"
><?php echo esc_html($tab_title); ?></button>
</li>
<?php $first = false; endforeach; ?>
</ul>
<?php $first = true; foreach ($tabs as $i => $tab) :
$tab_key = 'tab-' . $i;
?>
<div class="widget-tab-posts__panel<?php echo $first ? ' active' : ''; ?>"
data-tab-panel="<?php echo esc_attr($tab_key); ?>"
data-loaded="<?php echo $first ? '1' : '0'; ?>"
role="tabpanel"<?php echo $first ? '' : ' hidden'; ?>>
<?php if ($first) :
$opts = ['style' => $style, 'paginate' => $paginate];
$payload = self::render_tab_payload($tab, $count, $opts, 1);
$html = (string) ($payload['html'] ?? '');
if ($html !== '') {
echo '<div class="widget-tab-posts__list widget-tab-posts__list--' . esc_attr($style) . ' aksrt-main-posts style-' . esc_attr($style) . '">' . $html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if (!empty($payload['paginate_html'])) {
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
} else {
echo function_exists('aksrt_get_widget_null') ? aksrt_get_widget_null(__('暂无文章', 'aksrt')) : '<p class="widget-empty">' . esc_html__('暂无文章', 'aksrt') . '</p>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
else : ?>
<div class="post_ajax_loader" data-aksrt-ajax-loader><?php echo self::skeleton_html($count, $style); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
<?php endif; ?>
</div>
<?php $first = false; endforeach; ?>
</div>
<?php
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$title = $instance['title'] ?? '';
$count = (int) ($instance['count'] ?? 5);
$tabs = $this->normalize_tabs($instance['tabs'] ?? 'latest,comments,random');
$orderby_options = [
'date' => __('最新', 'aksrt'),
'modified' => __('更新', 'aksrt'),
'views' => __('浏览', 'aksrt'),
'likes' => __('点赞', 'aksrt'),
'comments' => __('评论', 'aksrt'),
'favorites' => __('收藏', 'aksrt'),
'downloads' => __('下载', 'aksrt'),
'rand' => __('随机', 'aksrt'),
];
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr((string) $title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('默认每个 Tab 显示数量:', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('count')); ?>" name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number" min="1" max="20" value="<?php echo (int) $count; ?>">
</p>
<p>
<label><?php esc_html_e('列表样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
<option value="mini" <?php selected(($instance['style'] ?? 'mini'), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
<option value="list" <?php selected(($instance['style'] ?? ''), 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
<option value="card" <?php selected(($instance['style'] ?? ''), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 加载更多', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
</select>
</label>
</p>
<hr style="margin:12px 0;">
<p style="font-weight:600;margin-bottom:8px;"><?php esc_html_e('Tab 配置', 'aksrt'); ?></p>
<?php
$max_tabs = 8;
$tabs = array_slice($tabs, 0, $max_tabs);
while (count($tabs) < 3) {
$tabs[] = ['title' => '', 'category_id' => 0, 'orderby' => 'date', 'count' => 0, 'limit_day' => 0, 'pay_type' => '', 'enabled' => false];
}
foreach ($tabs as $i => $tab) :
?>
<div style="border:1px solid #ddd;padding:8px;margin-bottom:8px;border-radius:4px;background:#fafafa;">
<p style="margin:0 0 6px;">
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][enabled]")); ?>" value="1" <?php checked(!empty($tab['enabled'])); ?>> <?php printf(esc_html__('Tab %d 启用', 'aksrt'), $i + 1); ?></label>
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('标题:', 'aksrt'); ?></label>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][title]")); ?>" value="<?php echo esc_attr($tab['title']); ?>" placeholder="<?php esc_attr_e('留空则使用分类名', 'aksrt'); ?>">
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('分类 ID:', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][category_id]")); ?>" value="<?php echo (int) $tab['category_id']; ?>" placeholder="0">
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('排序:', 'aksrt'); ?></label>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][orderby]")); ?>">
<?php foreach ($orderby_options as $val => $label) : ?>
<option value="<?php echo esc_attr($val); ?>" <?php selected($tab['orderby'], $val); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</p>
<p style="margin:0;">
<label><?php esc_html_e('数量(0=使用默认):', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="0" max="20" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][count]")); ?>" value="<?php echo (int) $tab['count']; ?>">
</p>
<p style="margin:0 0 6px;">
<label><?php esc_html_e('最近 N 天(0=不限):', 'aksrt'); ?></label>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][limit_day]")); ?>" value="<?php echo (int) ($tab['limit_day'] ?? 0); ?>">
</p>
<p style="margin:0;">
<label><?php esc_html_e('付费类型:', 'aksrt'); ?></label>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name("tabs[{$i}][pay_type]")); ?>">
<option value="" <?php selected(($tab['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
<option value="paid" <?php selected(($tab['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
<option value="reading" <?php selected(($tab['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
<option value="download" <?php selected(($tab['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
</select>
</p>
</div>
<?php endforeach;
}
public function update($new_instance, $old_instance): array {
$style = sanitize_key((string) ($new_instance['style'] ?? 'mini'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'mini';
}
$paginate = sanitize_key((string) ($new_instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
$result = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 5))),
'style' => $style,
'paginate' => $paginate,
];
$tabs = [];
if (isset($new_instance['tabs']) && is_array($new_instance['tabs'])) {
$allowed = ['date', 'modified', 'views', 'likes', 'comments', 'favorites', 'downloads', 'rand'];
foreach ($new_instance['tabs'] as $tab) {
if (!is_array($tab)) {
continue;
}
$orderby = sanitize_key($tab['orderby'] ?? 'date');
if (!in_array($orderby, $allowed, true)) {
$orderby = 'date';
}
$tabs[] = [
'title' => sanitize_text_field((string) ($tab['title'] ?? '')),
'category_id' => absint($tab['category_id'] ?? 0),
'orderby' => $orderby,
'order' => strtoupper(sanitize_key((string) ($tab['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'count' => max(0, min(20, (int) ($tab['count'] ?? 0))),
'limit_day' => max(0, (int) ($tab['limit_day'] ?? 0)),
'pay_type' => sanitize_key((string) ($tab['pay_type'] ?? '')),
'enabled' => !empty($tab['enabled']),
];
}
}
$result['tabs'] = !empty($tabs) ? $tabs : 'latest,comments,random';
return $result;
}
}
-192
View File
@@ -1,192 +0,0 @@
<?php
/**
* Tag cloud (zibll widget_ui_tag_cloud lite).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class TagCloudWidget extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_tag_cloud',
esc_html__('AKSRT 标签云', 'aksrt'),
[
'description' => esc_html__('展示文章标签/分类云', 'aksrt'),
'customize_selective_refresh' => true,
]
);
}
public static function render_html(array $instance): string {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'post_tag'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'post_tag';
}
$number = max(1, min(100, (int) ($instance['count'] ?? $instance['number'] ?? 20)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'name'));
// Zibll widget_ui_tag_cloud: name / count / term_id(创建时间).
if (!in_array($orderby, ['count', 'name', 'term_id', 'id'], true)) {
$orderby = 'name';
}
// Zibll `order` ASC/DESC (default ASC).
$order = strtoupper((string) ($instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC';
$show_count = !empty($instance['show_count']);
$blank = !empty($instance['blank']);
$color = sanitize_html_class((string) ($instance['color'] ?? 'rand'));
$fixed_width = !empty($instance['fixed_width']);
$wp_orderby = $orderby === 'id' ? 'term_id' : $orderby;
$tags = get_terms([
'taxonomy' => $taxonomy,
'orderby' => $wp_orderby,
'order' => $order,
'number' => $number,
'hide_empty' => !empty($instance['hide_empty']),
]);
if (is_wp_error($tags) || !$tags) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无标签', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无标签', 'aksrt') . '</p>';
}
$palette = ['c-blue', 'c-yellow', 'c-green', 'c-purple', 'c-red', '', 'c-blue-2', 'c-yellow-2', 'c-green-2', 'c-purple-2', 'c-red-2', ''];
$i = random_int(0, 10);
$scale = !empty($instance['scale']);
$min_c = null;
$max_c = null;
if ($scale) {
foreach ($tags as $t0) {
$c0 = (int) $t0->count;
$min_c = $min_c === null ? $c0 : min($min_c, $c0);
$max_c = $max_c === null ? $c0 : max($max_c, $c0);
}
}
$wrap = 'zib-widget widget-tag-cloud tag-cloud aksrt-tag-cloud author-tag ajax-item' . ($fixed_width ? ' fixed-width' : '');
ob_start();
echo '<div class="' . esc_attr($wrap) . '">';
foreach ($tags as $tag) {
$url = get_term_link($tag);
if (is_wp_error($url)) {
continue;
}
$cls = 'but';
if ($color === 'rand') {
if ($i > 10) {
$i = 0;
}
$cls .= ' ' . $palette[$i];
$i++;
} elseif ($color !== '') {
$cls .= ' ' . $color;
}
$tgt = $blank ? ' target="_blank" rel="noopener noreferrer"' : '';
$count_html = $show_count ? '<span class="em09 tag-count"> (' . (int) $tag->count . ')</span>' : '';
$style_attr = '';
if ($scale && $max_c !== null && $min_c !== null) {
$span = max(1, $max_c - $min_c);
$ratio = ((int) $tag->count - $min_c) / $span;
$em = round(0.85 + ($ratio * 0.4), 3);
$style_attr = ' style="font-size:' . esc_attr((string) $em) . 'em;--tag-scale:' . esc_attr((string) $em) . ';"';
}
echo '<a href="' . esc_url($url) . '" class="text-ellipsis ' . esc_attr(trim($cls)) . '"' . $tgt . $style_attr . '>' . esc_html($tag->name) . $count_html . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo self::render_html($instance); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$taxs = get_taxonomies(['public' => true], 'objects');
?>
<p>
<label><?php esc_html_e('标题:', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? __('标签云', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('分类法', 'aksrt'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('taxonomy')); ?>">
<?php foreach ($taxs as $tax) : ?>
<option value="<?php echo esc_attr($tax->name); ?>" <?php selected(($instance['taxonomy'] ?? 'post_tag'), $tax->name); ?>><?php echo esc_html($tax->labels->name); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('标签数量:', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="100" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 20))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="name" <?php selected(($instance['orderby'] ?? 'name'), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
<option value="count" <?php selected(($instance['orderby'] ?? ''), 'count'); ?>><?php esc_html_e('文章数量', 'aksrt'); ?></option>
<option value="term_id" <?php selected(($instance['orderby'] ?? ''), 'term_id'); ?>><?php esc_html_e('创建时间', 'aksrt'); ?></option>
</select>
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
<option value="ASC" <?php selected(($instance['order'] ?? 'ASC'), 'ASC'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
<option value="DESC" <?php selected(($instance['order'] ?? ''), 'DESC'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('颜色', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('color')); ?>">
<option value="rand" <?php selected(($instance['color'] ?? 'rand'), 'rand'); ?>><?php esc_html_e('随机颜色', 'aksrt'); ?></option>
<option value="c-hui" <?php selected(($instance['color'] ?? ''), 'c-hui'); ?>><?php esc_html_e('灰色', 'aksrt'); ?></option>
<option value="c-blue" <?php selected(($instance['color'] ?? ''), 'c-blue'); ?>><?php esc_html_e('蓝色', 'aksrt'); ?></option>
<option value="c-blue-2" <?php selected(($instance['color'] ?? ''), 'c-blue-2'); ?>><?php esc_html_e('深蓝色', 'aksrt'); ?></option>
<option value="c-cyan" <?php selected(($instance['color'] ?? ''), 'c-cyan'); ?>><?php esc_html_e('青色', 'aksrt'); ?></option>
<option value="c-yellow" <?php selected(($instance['color'] ?? ''), 'c-yellow'); ?>><?php esc_html_e('黄色', 'aksrt'); ?></option>
<option value="c-yellow-2" <?php selected(($instance['color'] ?? ''), 'c-yellow-2'); ?>><?php esc_html_e('橙黄色', 'aksrt'); ?></option>
<option value="c-green" <?php selected(($instance['color'] ?? ''), 'c-green'); ?>><?php esc_html_e('绿色', 'aksrt'); ?></option>
<option value="c-green-2" <?php selected(($instance['color'] ?? ''), 'c-green-2'); ?>><?php esc_html_e('墨绿色', 'aksrt'); ?></option>
<option value="c-purple" <?php selected(($instance['color'] ?? ''), 'c-purple'); ?>><?php esc_html_e('紫色', 'aksrt'); ?></option>
<option value="c-purple-2" <?php selected(($instance['color'] ?? ''), 'c-purple-2'); ?>><?php esc_html_e('深紫色', 'aksrt'); ?></option>
<option value="c-red" <?php selected(($instance['color'] ?? ''), 'c-red'); ?>><?php esc_html_e('粉红色', 'aksrt'); ?></option>
<option value="c-red-2" <?php selected(($instance['color'] ?? ''), 'c-red-2'); ?>><?php esc_html_e('红色', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_count')); ?>" value="1" <?php checked(!empty($instance['show_count'])); ?>> <?php esc_html_e('显示数量', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('新窗口', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('fixed_width')); ?>" value="1" <?php checked(!empty($instance['fixed_width'])); ?>> <?php esc_html_e('固定宽度', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空标签', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('scale')); ?>" value="1" <?php checked(!empty($instance['scale'])); ?>> <?php esc_html_e('按数量缩放字号', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'post_tag')),
'count' => max(1, min(100, (int) ($new_instance['count'] ?? 20))),
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'term_id', 'id'], true) ? $new_instance['orderby'] : 'name',
'order' => strtoupper((string) ($new_instance['order'] ?? 'ASC')) === 'DESC' ? 'DESC' : 'ASC',
'color' => sanitize_html_class((string) ($new_instance['color'] ?? 'rand')),
'show_count' => !empty($new_instance['show_count']) ? 1 : 0,
'blank' => !empty($new_instance['blank']) ? 1 : 0,
'fixed_width' => !empty($new_instance['fixed_width']) ? 1 : 0,
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
'scale' => !empty($new_instance['scale']) ? 1 : 0,
];
}
}
-3
View File
@@ -1,3 +0,0 @@
<?php
namespace AKSRT\Widgets;
require_once __DIR__ . '/MarketingBlocksWidget.php';
-175
View File
@@ -1,175 +0,0 @@
<?php
/**
* Announcement marquee → vertical notice swiper (zibll notice family parity).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetAnnouncement extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_announcement',
__('AKSRT 公告跑马灯', 'aksrt'),
[
'description' => __('滚动显示公告/通知(共享垂直 Swiper)。', 'aksrt'),
'classname' => 'widget_aksrt_announcement',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$items = !empty($instance['items']) ? (string) $instance['items'] : '';
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-bullhorn';
$speed = !empty($instance['speed']) ? max(5, min(30, (int) $instance['speed'])) : 10;
$mode = sanitize_key((string) ($instance['mode'] ?? 'swiper'));
if (!in_array($mode, ['swiper', 'marquee'], true)) {
$mode = 'swiper';
}
$interval = max(2, min(15, (int) ($instance['interval'] ?? max(3, (int) round($speed / 2)))));
$blank = !empty($instance['blank']);
$lines = array_values(array_filter(array_map('trim', explode("\n", $items))));
if (!$lines) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一条公告', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一条公告', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($mode === 'marquee') {
// Legacy CSS marquee path.
echo '<div class="aksrt-announcement">';
echo '<span class="aksrt-announcement__icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></span>';
echo '<div class="aksrt-announcement__track">';
echo '<ul class="aksrt-announcement__list" style="animation-duration: ' . esc_attr((string) $speed) . 's;">';
$render = function (string $line) use ($blank): void {
$parts = explode('|', $line, 2);
$text = trim($parts[0]);
$link = isset($parts[1]) ? trim($parts[1]) : '';
echo '<li class="aksrt-announcement__item">';
if ($link !== '') {
echo '<a href="' . esc_url($link) . '"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . '>' . esc_html($text) . '</a>';
} else {
echo esc_html($text);
}
echo '</li>';
};
foreach ($lines as $line) {
$render($line);
}
// Duplicate first for seamless CSS loop.
$render($lines[0]);
echo '</ul></div></div>';
} else {
// Shared vertical new-swiper (same stack as NoticeWidget).
echo '<div class="aksrt-scroll-notice aksrt-announcement-swiper-wrap swiper-bulletin">';
echo '<div class="aksrt-scroll-notice__icon bulletin-icon"><i class="' . esc_attr($icon) . '" aria-hidden="true"></i></div>';
echo '<div class="new-swiper aksrt-notice-swiper aksrt-announcement-swiper" data-direction="vertical" data-loop data-autoplay data-interval="' . esc_attr((string) ($interval * 1000)) . '">';
echo '<div class="swiper-wrapper">';
foreach ($lines as $line) {
$parts = explode('|', $line, 2);
$text = trim($parts[0]);
$link = isset($parts[1]) ? trim($parts[1]) : '';
echo '<div class="swiper-slide notice-slide">';
if ($link !== '') {
echo '<a class="text-ellipsis"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . ' href="' . esc_url($link) . '">' . esc_html($text) . '</a>';
} else {
echo '<span class="text-ellipsis">' . esc_html($text) . '</span>';
}
echo '</div>';
}
echo '</div></div></div>';
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$items = !empty($instance['items']) ? (string) $instance['items'] : '';
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-bullhorn';
$speed = !empty($instance['speed']) ? (int) $instance['speed'] : 10;
$mode = (string) ($instance['mode'] ?? 'swiper');
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题(可选):', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('items')); ?>"><?php esc_html_e('公告内容(每行一条):', 'aksrt'); ?></label>
<textarea class="widefat" id="<?php echo esc_attr($this->get_field_id('items')); ?>" name="<?php echo esc_attr($this->get_field_name('items')); ?>" rows="5" placeholder="系统维护通知&#10;新功能上线|https://example.com"><?php echo esc_textarea($items); ?></textarea>
<small><?php esc_html_e('每行一条,格式:文本 或 文本|链接', 'aksrt'); ?></small>
</p>
<p>
<label><?php esc_html_e('模式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('mode')); ?>">
<option value="swiper" <?php selected($mode, 'swiper'); ?>><?php esc_html_e('垂直轮播(推荐)', 'aksrt'); ?></option>
<option value="marquee" <?php selected($mode, 'marquee'); ?>><?php esc_html_e('横向跑马灯', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('icon')); ?>"><?php esc_html_e('图标:', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('icon')); ?>" name="<?php echo esc_attr($this->get_field_name('icon')); ?>" type="text" value="<?php echo esc_attr($icon); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('speed')); ?>"><?php esc_html_e('滚动速度(秒,跑马灯):', 'aksrt'); ?></label>
<input class="tiny-text" id="<?php echo esc_attr($this->get_field_id('speed')); ?>" name="<?php echo esc_attr($this->get_field_name('speed')); ?>" type="number" value="<?php echo esc_attr((string) $speed); ?>" min="5" max="30">
</p>
<p>
<label><?php esc_html_e('轮播间隔(秒)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="2" max="15" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['interval'] ?? 4))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('链接新窗口', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$mode = sanitize_key((string) ($new_instance['mode'] ?? 'swiper'));
if (!in_array($mode, ['swiper', 'marquee'], true)) {
$mode = 'swiper';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'items' => sanitize_textarea_field((string) ($new_instance['items'] ?? '')),
'icon' => sanitize_text_field((string) ($new_instance['icon'] ?? 'fas fa-bullhorn')),
'speed' => max(5, min(30, (int) ($new_instance['speed'] ?? 10))),
'mode' => $mode,
'interval' => max(2, min(15, (int) ($new_instance['interval'] ?? 4))),
'blank' => !empty($new_instance['blank']) ? 1 : 0,
]; return $business;
}
}
-117
View File
@@ -1,117 +0,0 @@
<?php
/**
* Post author card (zibll widget_ui_avatar + zib_get_user_card_box depth).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetAuthorCard extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_author_card',
esc_html__('AKSRT 文章作者卡片', 'aksrt'),
[
'description' => esc_html__('在文章/帖子侧栏显示当前内容作者信息。', 'aksrt'),
'classname' => 'widget_aksrt_author_card',
]
);
}
public function widget($args, $instance): void {
$author_id = 0;
if (is_singular()) {
$post = get_queried_object();
if ($post instanceof \WP_Post) {
$author_id = (int) $post->post_author;
}
}
if ($author_id <= 0) {
return;
}
$user = get_userdata($author_id);
if (!$user) {
return;
}
$show_stats = !isset($instance['show_stats']) || !empty($instance['show_stats']);
$avatar = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($author_id, ['size' => 120])
: get_avatar_url($author_id, ['size' => 120]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($author_id)
: get_author_posts_url($author_id);
$bio = get_user_meta($author_id, 'description', true);
if (!is_string($bio) || $bio === '') {
$bio = (string) $user->description;
}
$cover = (string) get_user_meta($author_id, 'aksrt_background_image', true);
if ($cover === '') {
$cover = get_theme_file_uri('assets/img/person_background.png');
}
$post_count = count_user_posts($author_id, 'post', true);
$followers = 0;
if (class_exists('\AKSRT\User\Follow')) {
$followers = \AKSRT\User\Follow::get_instance()->get_follower_count($author_id);
}
$name_html = function_exists('aksrt_get_user_name')
? aksrt_get_user_name(['id' => $author_id, 'follow' => false, 'class' => 'flex ac jc'])
: '<a href="' . esc_url($home) . '">' . esc_html($user->display_name) . '</a>';
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$card_cls = 'aksrt-author-card user-card author has-cover';
echo '<div class="' . esc_attr($card_cls) . '">';
$bg = ' style="background-image:url(' . esc_url($cover) . ')"';
echo '<div class="aksrt-author-card__cover user-cover"' . $bg . '></div>';
echo '<div class="aksrt-author-card__body card-content">';
echo '<div class="user-content">';
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
echo '<a class="aksrt-author-card__avatar user-avatar" href="' . esc_url($home) . '"><img class="avatar aksrt-lazy lazyload" data-src="' . esc_url($avatar) . '" src="' . esc_url($ph) . '" alt="" width="72" height="72" loading="lazy" /></a>';
echo '<div class="aksrt-author-card__name user-name">' . $name_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($bio !== '') {
echo '<div class="aksrt-author-card__bio user-desc muted-2-color em09">' . esc_html(wp_trim_words($bio, 28)) . '</div>';
}
if ($show_stats) {
echo '<div class="aksrt-author-card__stats">';
echo '<span><b>' . esc_html((string) $post_count) . '</b>' . esc_html__('文章', 'aksrt') . '</span>';
echo '<span><b>' . esc_html((string) $followers) . '</b>' . esc_html__('粉丝', 'aksrt') . '</span>';
echo '</div>';
}
$cuid = get_current_user_id();
echo '<div class="aksrt-author-card__actions user-btns">';
if ($cuid > 0 && $cuid !== $author_id && class_exists('\AKSRT\User\Follow')) {
$following = \AKSRT\User\Follow::get_instance()->is_following($cuid, $author_id);
$label = $following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
echo '<button type="button" class="aksrt-author-card__btn aksrt-author-card__follow but' . ($following ? ' is-following' : '') . '" data-aksrt-follow="' . (int) $author_id . '">' . esc_html($label) . '</button>';
}
echo '<a class="aksrt-author-card__btn but" href="' . esc_url($home) . '">' . esc_html__('查看主页', 'aksrt') . '</a>';
echo '</div>';
echo '</div>'; // .user-content
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
?>
<p class="description"><?php esc_html_e('自动读取当前文章/帖子作者。', 'aksrt'); ?></p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stats')); ?>" value="1" <?php checked(!isset($instance['show_stats']) || !empty($instance['show_stats'])); ?>> <?php esc_html_e('显示统计', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
return [
'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0,
];
}
}
-253
View File
@@ -1,253 +0,0 @@
<?php
/**
* Combo info cards (zibll zib_widget_ui_combo_info_card).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetComboInfo extends \WP_Widget {
private const BANDS = ['blue', 'cyan', 'green', 'yellow', 'pink', 'purple'];
public function __construct() {
parent::__construct(
'aksrt_combo_info',
esc_html__('AKSRT 复合信息卡片', 'aksrt'),
[
'description' => esc_html__('图标 + 标题 + 标签 + 描述 + CTA 的多列信息卡。', 'aksrt'),
'classname' => 'widget_aksrt_combo_info',
]
);
}
public function widget($args, $instance): void {
$cards = $this->normalize_cards($instance['cards'] ?? []);
if (!$cards) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一张组合信息卡', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一张组合信息卡', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$pc_row = max(1, min(6, (int) ($instance['pc_row'] ?? 4)));
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 1)));
$radius = max(0, min(40, (int) ($instance['card_radius'] ?? 12)));
$top_band = !empty($instance['top_band']) || !isset($instance['top_band']);
$band_h = max(20, min(160, (int) ($instance['top_band_height'] ?? 70)));
$cta_default = (string) ($instance['cta_text'] ?? __('立即体验', 'aksrt'));
$go_link = !empty($instance['go_link']);
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$style = sprintf('--aksrt-zci-radius:%dpx;--aksrt-zci-band-h:%dpx;--aksrt-zci-pc:%d;--aksrt-zci-m:%d;--zci-radius:%dpx;--zci-band-h:%dpx;', $radius, $band_h, $pc_row, $m_row, $radius, $band_h);
$wrap = 'aksrt-combo-info zib-combo-info-card row gutters-10' . ($top_band ? ' has-band zci-has-band' : '');
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$col = 'col-sm-' . $pc_span . ' col-xs-' . $m_span;
echo '<div class="' . esc_attr($wrap) . '" style="' . esc_attr($style) . '">';
$auto_idx = 0;
foreach ($cards as $card) {
$html = $this->render_card($card, $cta_default, $auto_idx, $go_link, $top_band);
if ($html === '') {
continue;
}
echo '<div class="' . esc_attr($col) . '">'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $card
*/
private function render_card(array $card, string $default_cta, int &$auto_idx, bool $go_link, bool $top_band): string {
$title = trim((string) ($card['title'] ?? ''));
$desc = trim((string) ($card['desc'] ?? ''));
if ($title === '' && $desc === '') {
return '';
}
$band = sanitize_key((string) ($card['band_color'] ?? 'auto'));
if ($band === '' || $band === 'auto' || !in_array($band, self::BANDS, true)) {
$band = self::BANDS[$auto_idx % count(self::BANDS)];
$auto_idx++;
}
$icon_html = '';
$icon_img = (string) ($card['icon_image'] ?? '');
$icon_fa = (string) ($card['icon'] ?? '');
if ($icon_img !== '') {
$icon_html = '<div class="aksrt-zci-icon"><img src="' . esc_url($icon_img) . '" alt="" loading="lazy" /></div>';
} elseif ($icon_fa !== '') {
$icon_html = '<div class="aksrt-zci-icon"><i class="' . esc_attr($icon_fa) . '" aria-hidden="true"></i></div>';
}
$tags_html = '';
$tags = $card['tags'] ?? [];
if (is_string($tags)) {
$tags = array_filter(array_map('trim', explode(',', $tags)));
$tags = array_map(static fn($t) => ['text' => $t, 'color' => ''], $tags);
}
if (is_array($tags) && $tags) {
$tags_html .= '<div class="aksrt-zci-tags">';
foreach ($tags as $tag) {
if (is_string($tag)) {
$tag = ['text' => $tag, 'color' => ''];
}
if (!is_array($tag)) {
continue;
}
$tt = trim((string) ($tag['text'] ?? ''));
if ($tt === '') {
continue;
}
$color = sanitize_html_class((string) ($tag['color'] ?? ''));
$tags_html .= '<span class="aksrt-zci-tag' . ($color !== '' ? ' ' . esc_attr($color) : '') . '">' . esc_html($tt) . '</span>';
}
$tags_html .= '</div>';
}
$cta_text = trim((string) ($card['cta_text'] ?? ''));
if ($cta_text === '') {
$cta_text = $default_cta;
}
$url = (string) ($card['url'] ?? '');
if ($url === '' && is_array($card['link'] ?? null)) {
$url = (string) ($card['link']['url'] ?? '');
}
$target = (string) ($card['target'] ?? '');
if ($target === '' && is_array($card['link'] ?? null)) {
$target = (string) ($card['link']['target'] ?? '');
}
$cta_html = '';
if ($cta_text !== '' && $url !== '') {
if ($go_link && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
$tgt = $target !== '' ? ' target="' . esc_attr($target) . '" rel="noopener noreferrer"' : '';
$cta_html = '<a class="aksrt-zci-cta" href="' . esc_url($url) . '"' . $tgt . '><span>' . esc_html($cta_text) . '</span><i class="fa fa-arrow-right" aria-hidden="true"></i></a>';
}
$band_class = $top_band ? ' aksrt-zci-band-' . esc_attr($band) : '';
$html = '<div class="aksrt-zci-card' . $band_class . '">';
$html .= $icon_html;
if ($title !== '') {
$html .= '<div class="aksrt-zci-title">' . esc_html($title) . '</div>';
}
$html .= $tags_html;
if ($desc !== '') {
$html .= '<div class="aksrt-zci-desc">' . esc_html($desc) . '</div>';
}
$html .= $cta_html;
$html .= '</div>';
return $html;
}
/**
* @param mixed $raw
* @return array<int, array<string,mixed>>
*/
private function normalize_cards($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$title = sanitize_text_field((string) ($row['title'] ?? ''));
$desc = sanitize_text_field((string) ($row['desc'] ?? ''));
if ($title === '' && $desc === '') {
continue;
}
$out[] = [
'title' => $title,
'desc' => $desc,
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
'icon_image' => esc_url_raw(trim((string) ($row['icon_image'] ?? ''))),
'url' => esc_url_raw(trim((string) ($row['url'] ?? (is_array($row['link'] ?? null) ? ($row['link']['url'] ?? '') : '')))),
'target' => sanitize_text_field((string) ($row['target'] ?? '_blank')),
'cta_text' => sanitize_text_field((string) ($row['cta_text'] ?? '')),
'band_color' => sanitize_key((string) ($row['band_color'] ?? 'auto')),
'tags' => $row['tags'] ?? [],
];
if (count($out) >= 12) {
break;
}
}
return $out;
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$cards = is_string($instance['cards'] ?? null)
? (string) $instance['cards']
: wp_json_encode($this->normalize_cards($instance['cards'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('卡片 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="8" name="<?php echo esc_attr($this->get_field_name('cards')); ?>" placeholder='[{"title":"功能","desc":"说明","icon":"fas fa-star","url":"https://","cta_text":"了解更多","tags":[{"text":"热门","color":"c-blue"}]}]'><?php echo esc_textarea((string) $cards); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('PC 列数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? 4))); ?>">
</label>
<label><?php esc_html_e('移动列数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('默认 CTA 文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('cta_text')); ?>" value="<?php echo esc_attr((string) ($instance['cta_text'] ?? __('立即体验', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('top_band')); ?>" value="1" <?php checked(!empty($instance['top_band']) || !isset($instance['top_band'])); ?>> <?php esc_html_e('顶部色带', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'cards' => $this->normalize_cards($new_instance['cards'] ?? []),
'pc_row' => max(1, min(6, (int) ($new_instance['pc_row'] ?? 4))),
'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 1))),
'card_radius' => max(0, min(40, (int) ($new_instance['card_radius'] ?? 12))),
'top_band' => !empty($new_instance['top_band']) ? 1 : 0,
'top_band_height' => max(20, min(160, (int) ($new_instance['top_band_height'] ?? 70))),
'cta_text' => sanitize_text_field((string) ($new_instance['cta_text'] ?? '')),
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
]; return $business;
}
}
-227
View File
@@ -1,227 +0,0 @@
<?php
/**
* Graphic cover cards (zibll zib_widget_ui_graphic_cover → aksrt_graphic_card).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetCoverCards extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_cover_cards',
__('AKSRT 图文封面卡片', 'aksrt'),
['description' => __('图片/视频封面 + 文字叠加的图文卡片(共享 graphic_card)。', 'aksrt')]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$items = $this->normalize($instance['items'] ?? []);
if (!$items) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一张封面卡片', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一张封面卡片', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$pc_row = max(1, min(6, (int) ($instance['pc_row'] ?? $instance['columns'] ?? 2)));
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 1)));
$height_scale = max(30, min(120, (int) ($instance['height_scale'] ?? 70)));
$mask_opacity = max(0, min(80, (int) ($instance['mask_opacity'] ?? 20)));
$font_size_pc = max(12, min(32, (int) ($instance['font_size_pc'] ?? 18)));
$font_size_m = max(11, min(24, (int) ($instance['font_size_m'] ?? 14)));
$font_bold = !empty($instance['font_bold']);
$font_color = sanitize_hex_color((string) ($instance['font_color'] ?? '')) ?: '';
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$is_row = count($items) > 1;
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
$style = '';
if ($font_size_pc !== 14) {
$style .= '--font-size:' . $font_size_pc . 'px;';
}
if ($font_size_m !== 14) {
$style .= '--font-size-sm:' . $font_size_m . 'px;';
}
if ($font_bold) {
$style .= '--font-weight:bold;--font-weight-sm:bold;';
}
if ($font_color !== '') {
$style .= '--color:' . $font_color . ';--color-sm:' . $font_color . ';';
}
echo '<div class="widget-graphic-cover aksrt-cover-cards' . ($is_row ? ' mb10' : ' mb20') . '"' . ($style !== '' ? ' style="' . esc_attr($style) . '"' : '') . '>';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($items as $item) {
$hide = sanitize_key((string) ($item['hide'] ?? ''));
if ($hide === 'pc' || $hide === 'm') {
$is_mobile = function_exists('wp_is_mobile') ? wp_is_mobile() : false;
if ((!$is_mobile && $hide === 'pc') || ($is_mobile && $hide === 'm')) {
continue;
}
}
$card_title = (string) ($item['title'] ?? '');
$more = $card_title !== ''
? '<div class="abs-center text-center graphic-text this-font">' . esc_html($card_title) . '</div>'
: '';
$card = [
'class' => 'noshadow mb10',
'img' => (string) ($item['image'] ?? ''),
'video' => (string) ($item['video'] ?? ''),
'alt' => wp_strip_all_tags($card_title),
'link' => [
'url' => (string) ($item['link'] ?? ''),
'target' => (string) ($item['target'] ?? ''),
],
'lazy' => true,
'more' => $more,
'height_scale' => !empty($item['height']) && is_numeric($item['height'])
? (int) $item['height']
: $height_scale,
'mask_opacity' => isset($item['mask']) ? (int) $item['mask'] : $mask_opacity,
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">';
}
if (function_exists('aksrt_graphic_card')) {
echo aksrt_graphic_card($card); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array<string,mixed>>
*/
private function normalize($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$image = esc_url_raw(trim((string) ($row['image'] ?? '')));
$video = esc_url_raw(trim((string) ($row['video'] ?? '')));
if ($image === '' && $video === '') {
continue;
}
$out[] = [
'title' => sanitize_text_field((string) ($row['title'] ?? '')),
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
'image' => $image,
'video' => $video,
'link' => esc_url_raw(trim((string) ($row['link'] ?? ''))),
'target' => sanitize_text_field((string) ($row['target'] ?? '')),
'hide' => sanitize_key((string) ($row['hide'] ?? '')),
'height' => isset($row['height']) ? (int) $row['height'] : 0,
'mask' => isset($row['mask']) ? (int) $row['mask'] : null,
];
if (count($out) >= 12) {
break;
}
}
return $out;
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$items = $this->normalize($instance['items'] ?? []);
$json = wp_json_encode($items, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<p>
<label><?php esc_html_e('标题(可选):', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('PC 列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? $instance['columns'] ?? 2))); ?>">
</label>
<label><?php esc_html_e('移动列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="30" max="120" name="<?php echo esc_attr($this->get_field_name('height_scale')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['height_scale'] ?? 70))); ?>">
</label>
<label><?php esc_html_e('遮罩 0-80', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="80" name="<?php echo esc_attr($this->get_field_name('mask_opacity')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mask_opacity'] ?? 20))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('卡片 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="6" name="<?php echo esc_attr($this->get_field_name('items')); ?>" placeholder='[{"image":"https://...","title":"标题","link":"https://..."}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
<small><?php esc_html_e('字段: image, video, title, link, target, hide(pc|m), height, mask', 'aksrt'); ?></small>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'pc_row' => max(1, min(6, (int) ($new_instance['pc_row'] ?? 2))),
'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 1))),
'columns' => max(1, min(6, (int) ($new_instance['pc_row'] ?? $new_instance['columns'] ?? 2))),
'height_scale' => max(30, min(120, (int) ($new_instance['height_scale'] ?? 70))),
'mask_opacity' => max(0, min(80, (int) ($new_instance['mask_opacity'] ?? 20))),
'font_size_pc' => max(12, min(32, (int) ($new_instance['font_size_pc'] ?? 18))),
'font_size_m' => max(11, min(24, (int) ($new_instance['font_size_m'] ?? 14))),
'font_bold' => !empty($new_instance['font_bold']) ? 1 : 0,
'font_color' => sanitize_hex_color((string) ($new_instance['font_color'] ?? '')) ?: '',
'items' => $this->normalize($new_instance['items'] ?? []),
]; return $business;
}
}
-207
View File
@@ -1,207 +0,0 @@
<?php
/**
* Icon cover cards (zibll zib_widget_ui_icon_cover_card → aksrt_icon_cover_card).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetIconCoverCards extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_icon_cover_cards',
esc_html__('AKSRT 图标封面卡', 'aksrt'),
[
'description' => esc_html__('横向图标 + 标题/描述的导航卡片。', 'aksrt'),
'classname' => 'widget_aksrt_icon_cover_cards',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$cards = $this->normalize($instance['cards'] ?? []);
if (!$cards) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请添加至少一张图标封面卡', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请添加至少一张图标封面卡', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$pc_row = max(1, min(4, (int) ($instance['pc_row'] ?? 2)));
$m_row = max(1, min(2, (int) ($instance['m_row'] ?? 1)));
$icon_size = max(16, min(48, (int) ($instance['icon_size'] ?? 25)));
$radius = !empty($instance['icon_radius4']);
$go_link = !empty($instance['go_link']);
$show_bg = !empty($instance['show_widget_bg']);
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$is_row = count($cards) > 1;
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
echo $show_bg ? '<div class="zib-widget padding-10">' : '<div class="mb10">';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($cards as $card) {
$url = (string) ($card['url'] ?? '');
if ($url !== '' && $go_link && function_exists('aksrt_go_link')) {
$url = aksrt_go_link($url, true);
}
$card_args = [
'class' => $show_bg ? 'padding-10' : 'zib-widget mb10',
'icon' => (string) ($card['icon'] ?? ''),
'icon_image' => (string) ($card['icon_image'] ?? ''),
'icon_size' => $icon_size,
'icon_class' => $radius ? 'radius4' : '',
'icon_color' => (string) ($card['icon_color'] ?? ''),
'title' => (string) ($card['title'] ?? ''),
'desc' => (string) ($card['desc'] ?? ''),
'link' => [
'url' => $url,
'target' => (string) ($card['target'] ?? ''),
],
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">';
}
if (function_exists('aksrt_icon_cover_card')) {
echo aksrt_icon_cover_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param mixed $raw
* @return array<int, array<string,string>>
*/
private function normalize($raw): array {
if (is_string($raw)) {
$decoded = json_decode($raw, true);
$raw = is_array($decoded) ? $decoded : [];
}
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$title = sanitize_text_field((string) ($row['title'] ?? ''));
$icon = sanitize_text_field((string) ($row['icon'] ?? ''));
$icon_image = esc_url_raw(trim((string) ($row['icon_image'] ?? $row['customize_icon'] ?? '')));
if ($title === '' && $icon === '' && $icon_image === '') {
continue;
}
$url = '';
$target = '';
if (is_array($row['link'] ?? null)) {
$url = esc_url_raw(trim((string) ($row['link']['url'] ?? '')));
$target = sanitize_text_field((string) ($row['link']['target'] ?? ''));
} else {
$url = esc_url_raw(trim((string) ($row['url'] ?? $row['link'] ?? '')));
$target = sanitize_text_field((string) ($row['target'] ?? '_blank'));
}
$out[] = [
'title' => $title,
'desc' => sanitize_text_field((string) ($row['desc'] ?? '')),
'icon' => $icon,
'icon_image' => $icon_image,
'icon_color' => sanitize_html_class((string) ($row['icon_color'] ?? '')),
'url' => $url,
'target' => $target,
];
if (count($out) >= 16) {
break;
}
}
return $out;
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$json = is_string($instance['cards'] ?? null)
? (string) $instance['cards']
: wp_json_encode($this->normalize($instance['cards'] ?? []), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('卡片 JSON', 'aksrt'); ?>
<textarea class="widefat" rows="7" name="<?php echo esc_attr($this->get_field_name('cards')); ?>" placeholder='[{"title":"文档","desc":"使用说明","icon":"fas fa-book","url":"https://"}]'><?php echo esc_textarea((string) $json); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('PC 列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? 2))); ?>">
</label>
<label><?php esc_html_e('移动列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="2" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
</label>
<label><?php esc_html_e('图标字号', 'aksrt'); ?>
<input class="tiny-text" type="number" min="16" max="48" name="<?php echo esc_attr($this->get_field_name('icon_size')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['icon_size'] ?? 25))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('icon_radius4')); ?>" value="1" <?php checked(!empty($instance['icon_radius4'])); ?>> <?php esc_html_e('图标圆角方形', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_widget_bg')); ?>" value="1" <?php checked(!empty($instance['show_widget_bg'])); ?>> <?php esc_html_e('模块背景', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('go_link')); ?>" value="1" <?php checked(!empty($instance['go_link'])); ?>> <?php esc_html_e('外链跳转页', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'cards' => $this->normalize($new_instance['cards'] ?? []),
'pc_row' => max(1, min(4, (int) ($new_instance['pc_row'] ?? 2))),
'm_row' => max(1, min(2, (int) ($new_instance['m_row'] ?? 1))),
'icon_size' => max(16, min(48, (int) ($new_instance['icon_size'] ?? 25))),
'icon_radius4' => !empty($new_instance['icon_radius4']) ? 1 : 0,
'show_widget_bg' => !empty($new_instance['show_widget_bg']) ? 1 : 0,
'go_link' => !empty($new_instance['go_link']) ? 1 : 0,
]; return $business;
}
}
-157
View File
@@ -1,157 +0,0 @@
<?php
/**
* Super embed / iframe widget (zibll zib_widget_ui_iframe).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetIframe extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_iframe',
esc_html__('AKSRT 超级嵌入', 'aksrt'),
[
'description' => esc_html__('嵌入在线视频/音乐播放器或任意 iframe 内容。', 'aksrt'),
'classname' => 'widget_aksrt_iframe',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$raw = trim((string) ($instance['url'] ?? ''));
if ($raw === '') {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请配置 URL 或 iframe 代码', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<div class="c-red muted-box mb20"><b>' . esc_html__('超级嵌入:', 'aksrt') . '</b>' . esc_html__('请配置 URL 或 iframe 代码', 'aksrt') . '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$aspect = max(20, min(300, (int) ($instance['aspect'] ?? 55)));
$allow_fs = !empty($instance['allowfullscreen']);
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$iframe_html = '';
if (stripos($raw, '<iframe') !== false && stripos($raw, '</iframe>') !== false) {
$iframe_html = wp_kses(
$raw,
[
'iframe' => [
'src' => true,
'width' => true,
'height' => true,
'frameborder' => true,
'allow' => true,
'allowfullscreen' => true,
'loading' => true,
'class' => true,
'style' => true,
'referrerpolicy' => true,
'title' => true,
'sandbox' => true,
'data-src' => true,
'framespacing' => true,
'border' => true,
],
]
);
// Prefer lazy: if iframe has src but no data-src, convert.
if (preg_match('/<iframe\b[^>]*>/i', $iframe_html, $m)
&& stripos($m[0], 'data-src=') === false
&& preg_match('/\ssrc=["\']([^"\']+)["\']/i', $m[0], $sm)
) {
$src = $sm[1];
$tag = preg_replace('/\ssrc=["\'][^"\']*["\']/i', ' src="" data-src="' . esc_url($src) . '"', $m[0], 1);
if (is_string($tag) && stripos($tag, 'lazyload') === false) {
if (preg_match('/\sclass=["\']([^"\']*)["\']/', $tag)) {
$tag = preg_replace('/\sclass=["\']([^"\']*)["\']/', ' class="$1 lazyload aksrt-lazy"', $tag, 1);
} else {
$tag = preg_replace('/^<iframe\b/i', '<iframe class="lazyload aksrt-lazy"', $tag, 1);
}
}
if (is_string($tag)) {
$iframe_html = str_replace($m[0], $tag, $iframe_html);
}
}
} else {
$url = esc_url($raw);
if ($url === '') {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
$fs = $allow_fs ? ' allowfullscreen="allowfullscreen"' : '';
// zibll: class="lazyload" + data-src (not immediate src).
$iframe_html = '<iframe class="lazyload aksrt-lazy aksrt-iframe-el" src="" data-src="' . $url . '" loading="lazy" frameborder="0" framespacing="0" border="0"' . $fs . '></iframe>';
}
// zibll shell: mb20 > wp-block-embed is-type-video relative-h radius8 > padding-bottom.
echo '<div class="mb20 aksrt-iframe-embed">';
echo '<div class="wp-block-embed is-type-video relative-h radius8">';
echo '<div class="aksrt-iframe-ratio" style="padding-bottom:' . esc_attr((string) $aspect) . '%">';
echo $iframe_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div></div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$url = (string) ($instance['url'] ?? '');
$aspect = (int) ($instance['aspect'] ?? 55);
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('嵌入地址或 iframe 代码', 'aksrt'); ?>
<textarea class="widefat" rows="4" name="<?php echo esc_attr($this->get_field_name('url')); ?>"><?php echo esc_textarea($url); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="20" max="300" step="5" name="<?php echo esc_attr($this->get_field_name('aspect')); ?>" value="<?php echo esc_attr((string) $aspect); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('allowfullscreen')); ?>" value="1" <?php checked(!empty($instance['allowfullscreen'])); ?>> <?php esc_html_e('允许全屏', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'url' => trim((string) ($new_instance['url'] ?? '')),
'aspect' => max(20, min(300, (int) ($new_instance['aspect'] ?? 55))),
'allowfullscreen' => !empty($new_instance['allowfullscreen']) ? 1 : 0,
]; return $business;
}
}
-459
View File
@@ -1,459 +0,0 @@
<?php
/**
* Main posts list (zibll zib_widget_ui_main_post + ajax pager).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetMainPosts extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_main_posts',
esc_html__('AKSRT 文章列表', 'aksrt'),
[
'description' => esc_html__('卡片/迷你/列表样式的文章模块(支持 AJAX)。', 'aksrt'),
'classname' => 'widget_aksrt_main_posts',
]
);
}
/**
* @param array $instance
* @param int $paged
* @return array
*/
public static function build_query_args(array $instance, int $paged = 1): array {
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$cat = (int) ($instance['cat'] ?? 0);
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
// Zibll `order` parity: asc/desc. Default desc.
$order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC';
$query = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'paged' => max(1, $paged),
'ignore_sticky_posts' => true,
];
if ($cat > 0) {
$query['cat'] = $cat;
}
if ($limit_day > 0) {
$query['date_query'] = [['after' => $limit_day . ' days ago']];
}
switch ($orderby) {
case 'views':
$query['meta_key'] = 'aksrt_post_views';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'comments':
$query['orderby'] = 'comment_count';
$query['order'] = $order;
break;
case 'likes':
$query['meta_key'] = '_aksrt_like_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'favorites':
$query['meta_key'] = '_aksrt_favorite_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'downloads':
$query['meta_key'] = '_aksrt_res_download_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'rand':
$query['orderby'] = 'rand';
break;
case 'modified':
$query['orderby'] = 'modified';
$query['order'] = $order;
break;
default:
$query['orderby'] = 'date';
$query['order'] = $order;
}
// Pay type filter (reading/download/gallery/video/vip or any paid).
$pay_type = sanitize_key((string) ($instance['pay_type'] ?? ''));
if ($pay_type !== '' && $pay_type !== '0' && $pay_type !== 'no') {
$mq = isset($query['meta_query']) && is_array($query['meta_query']) ? $query['meta_query'] : [];
if ($pay_type === 'any' || $pay_type === 'paid') {
$mq[] = [
'relation' => 'OR',
[
'key' => '_aksrt_pay_price',
'value' => 0,
'compare' => '>',
'type' => 'NUMERIC',
],
[
'key' => '_aksrt_pay_type',
'value' => ['reading', 'download', 'gallery', 'video', 'vip'],
'compare' => 'IN',
],
[
'key' => '_aksrt_visibility',
'value' => ['pay', 'credits'],
'compare' => 'IN',
],
];
} else {
$mq[] = [
'key' => '_aksrt_pay_type',
'value' => $pay_type,
];
}
if ($mq) {
if (count($mq) > 1) {
$mq['relation'] = 'AND';
}
$query['meta_query'] = $mq;
}
}
return $query;
}
public static function render_items_html(\WP_Query $q, array $instance): string {
if (!$q->have_posts()) {
return '';
}
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'list';
}
$show_thumb = !isset($instance['show_thumb']) || !empty($instance['show_thumb']);
$show_meta = !isset($instance['show_meta']) || !empty($instance['show_meta']);
$show_number = !empty($instance['show_number']);
$start = max(0, (int) ($instance['_start_index'] ?? 0));
ob_start();
// Outer wrapper only on page 1 full render handled by caller for ajax append items.
$mode = $style === 'card' ? 'card' : ($style === 'mini' ? 'mini' : 'list');
if (function_exists('aksrt_render_post')) {
$html = '';
$i = $start;
while ($q->have_posts()) {
$q->the_post();
$i++;
$html .= aksrt_render_post([
'mode' => $mode,
'context' => 'widget',
'show_thumb' => $show_thumb,
'show_meta' => $show_meta,
'show_excerpt' => $mode === 'list',
'show_number' => $show_number && $mode === 'mini',
'number_offset' => $show_number ? $i : 0,
'extra_class' => 'ajax-item',
]);
}
wp_reset_postdata();
return $html;
}
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
$i = $start;
while ($q->have_posts()) {
$q->the_post();
$i++;
$thumb = $show_thumb ? get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail') : '';
$views = (int) get_post_meta(get_the_ID(), 'aksrt_post_views', true);
if ($style === 'card') {
echo '<a class="aksrt-main-posts__card ajax-item" href="' . esc_url(get_permalink()) . '">';
echo '<div class="aksrt-main-posts__thumb' . ($thumb ? '' : ' is-empty') . '">';
if ($thumb) {
echo '<img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
}
echo '</div>';
echo '<div class="aksrt-main-posts__body">';
echo '<div class="aksrt-main-posts__title">' . esc_html(get_the_title()) . '</div>';
if ($show_meta) {
echo '<div class="aksrt-main-posts__meta"><span>' . esc_html(get_the_date()) . '</span>';
if ($views > 0) {
echo '<span><i class="fas fa-eye"></i> ' . esc_html((string) $views) . '</span>';
}
echo '</div>';
}
echo '</div></a>';
} else {
echo '<a class="aksrt-main-posts__row ajax-item" href="' . esc_url(get_permalink()) . '">';
if ($show_number) {
echo '<span class="aksrt-main-posts__num">' . esc_html((string) $i) . '</span>';
}
if ($show_thumb) {
echo '<span class="aksrt-main-posts__thumb mini' . ($thumb ? '' : ' is-empty') . '">';
if ($thumb) {
echo '<img class="aksrt-lazy lazyload" data-src="' . esc_url($thumb) . '" src="' . esc_url($placeholder) . '" alt="" loading="lazy" />';
}
echo '</span>';
}
echo '<span class="aksrt-main-posts__body">';
echo '<span class="aksrt-main-posts__title">' . esc_html(get_the_title()) . '</span>';
if ($show_meta) {
echo '<span class="aksrt-main-posts__meta">' . esc_html(get_the_date()) . '</span>';
}
echo '</span></a>';
}
}
wp_reset_postdata();
return (string) ob_get_clean();
}
/**
* @return array{html:string,paginate_html:string,has_more:bool,page:int,max_pages:int,total:int}
*/
public static function query_payload(array $instance, int $page = 1): array {
$page = max(1, $page);
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
$instance['_start_index'] = ($page - 1) * $count;
$q = new \WP_Query(self::build_query_args($instance, $page));
$html = self::render_items_html($q, $instance);
if ($html === '' && $page === 1) {
$html = function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="aksrt-main-posts__empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
$max_pages = max(1, (int) $q->max_num_pages);
$total = (int) $q->found_posts;
$has_more = $page < $max_pages;
$base_qs = array_filter([
'count' => $count,
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => (int) ($instance['cat'] ?? 0),
'limit_day' => (int) ($instance['limit_day'] ?? 0),
'pay_type' => sanitize_key((string) ($instance['pay_type'] ?? '')),
'style' => sanitize_key((string) ($instance['style'] ?? 'list')),
'show_thumb' => !isset($instance['show_thumb']) || !empty($instance['show_thumb']) ? 1 : 0,
'show_meta' => !isset($instance['show_meta']) || !empty($instance['show_meta']) ? 1 : 0,
'show_number' => !empty($instance['show_number']) ? 1 : 0,
'paginate' => $paginate,
'load_mode' => sanitize_key((string) ($instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail',
], static function ($v) {
return $v !== '' && $v !== null;
});
$rest_base = add_query_arg($base_qs, rest_url('aksrt/v1/widgets/posts'));
$paginate_html = '';
if ($paginate === 'ajax' && $has_more) {
$next = add_query_arg(['page' => $page + 1], $rest_base);
$paginate_html = function_exists('aksrt_get_ajax_next_paginate')
? aksrt_get_ajax_next_paginate($total, $page, $count, $next)
: '<div class="text-center theme-pagination ajax-pag"><div class="next-page ajax-next"><a href="' . esc_url($next) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
} elseif ($paginate === 'number' && $max_pages > 1) {
$paginate_html = function_exists('aksrt_get_ajax_number_paginate')
? aksrt_get_ajax_number_paginate($total, $page, $count, $rest_base)
: '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
} else {
$paginate_html = '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
}
return [
'html' => $html,
'paginate_html' => $paginate_html,
'has_more' => $has_more,
'page' => $page,
'max_pages' => $max_pages,
'total' => $total,
];
}
public function widget($args, $instance): void {
$style = sanitize_key((string) ($instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'list';
}
$paginate = sanitize_key((string) ($instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
$load_mode = sanitize_key((string) ($instance['load_mode'] ?? 'detail'));
if (!in_array($load_mode, ['detail', 'ajax'], true)) {
$load_mode = 'detail';
}
$instance['style'] = $style;
$instance['paginate'] = $paginate;
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
$count = max(1, min(30, (int) ($instance['count'] ?? 6)));
$rest = rest_url('aksrt/v1/widgets/posts');
$qs = http_build_query(array_filter([
'page' => 1,
'count' => $count,
'orderby' => sanitize_key((string) ($instance['orderby'] ?? 'date')),
'order' => strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => (int) ($instance['cat'] ?? 0),
'limit_day' => (int) ($instance['limit_day'] ?? 0),
'pay_type' => sanitize_key((string) ($instance['pay_type'] ?? '')),
'style' => $style,
'show_thumb' => !isset($instance['show_thumb']) || !empty($instance['show_thumb']) ? 1 : 0,
'show_meta' => !isset($instance['show_meta']) || !empty($instance['show_meta']) ? 1 : 0,
'show_number' => !empty($instance['show_number']) ? 1 : 0,
'paginate' => $paginate,
'load_mode' => sanitize_key((string) ($instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail',
], static function ($v) {
return $v !== '' && $v !== null;
}));
$wrap_cls = 'aksrt-main-posts style-' . $style . ' ajaxpager';
if ($style === 'mini') {
$wrap_cls .= ' zib-widget posts-mini-lists';
}
echo '<div class="' . esc_attr($wrap_cls) . '" data-aksrt-ajaxpager data-aksrt-ajax-url="' . esc_url($rest) . '" data-load-mode="' . esc_attr($load_mode) . '" data-query="' . esc_attr($qs) . '"' . ($load_mode === 'ajax' ? ' data-loaded="0"' : ' data-loaded="1"') . '>';
if ($load_mode === 'ajax') {
// Skeleton first paint (zibll load_mode=ajax).
echo '<div class="aksrt-main-posts__skeleton">';
for ($si = 0; $si < $count; $si++) {
if ($style === 'card') {
echo '<div class="placeholder-row card"><div class="placeholder thumb"></div><div class="placeholder k1"></div></div>';
} else {
echo '<div class="posts-mini placeholder-row"><div class="placeholder radius thumb-placeholder"></div><div class="flex1"><div class="placeholder t1 k1"></div><div class="placeholder s1"></div></div></div>';
}
}
echo '</div>';
} else {
$payload = self::query_payload($instance, 1);
echo $payload['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $payload['paginate_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '<div class="post_ajax_loader" data-aksrt-ajax-loader data-aksrt-ajax-loader-hide hidden></div>';
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="30" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 6))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
<option value="list" <?php selected(($instance['style'] ?? 'list'), 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
<option value="mini" <?php selected(($instance['style'] ?? ''), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
<option value="card" <?php selected(($instance['style'] ?? ''), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="comments" <?php selected(($instance['orderby'] ?? ''), 'comments'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
<option value="likes" <?php selected(($instance['orderby'] ?? ''), 'likes'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
<option value="favorites" <?php selected(($instance['orderby'] ?? ''), 'favorites'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
<option value="downloads" <?php selected(($instance['orderby'] ?? ''), 'downloads'); ?>><?php esc_html_e('下载', 'aksrt'); ?></option>
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
</select>
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
<option value="desc" <?php selected(($instance['order'] ?? 'desc'), 'desc'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
<option value="asc" <?php selected(($instance['order'] ?? ''), 'asc'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('分类 ID(0=全部)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['cat'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('最近 N 天(0=不限)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('limit_day')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit_day'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('模块加载', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('load_mode')); ?>">
<option value="detail" <?php selected(($instance['load_mode'] ?? 'detail'), 'detail'); ?>><?php esc_html_e('直接加载', 'aksrt'); ?></option>
<option value="ajax" <?php selected(($instance['load_mode'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 懒加载', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('分页', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('paginate')); ?>">
<option value="none" <?php selected(($instance['paginate'] ?? 'none'), 'none'); ?>><?php esc_html_e('无', 'aksrt'); ?></option>
<option value="ajax" <?php selected(($instance['paginate'] ?? ''), 'ajax'); ?>><?php esc_html_e('AJAX 加载更多', 'aksrt'); ?></option>
<option value="number" <?php selected(($instance['paginate'] ?? ''), 'number'); ?>><?php esc_html_e('数字分页', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('付费类型', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('pay_type')); ?>">
<option value="" <?php selected(($instance['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
<option value="paid" <?php selected(($instance['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
<option value="reading" <?php selected(($instance['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
<option value="download" <?php selected(($instance['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
<option value="gallery" <?php selected(($instance['pay_type'] ?? ''), 'gallery'); ?>><?php esc_html_e('付费图库', 'aksrt'); ?></option>
<option value="video" <?php selected(($instance['pay_type'] ?? ''), 'video'); ?>><?php esc_html_e('付费视频', 'aksrt'); ?></option>
<option value="vip" <?php selected(($instance['pay_type'] ?? ''), 'vip'); ?>><?php esc_html_e('会员专属', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_thumb')); ?>" value="1" <?php checked(!isset($instance['show_thumb']) || !empty($instance['show_thumb'])); ?>> <?php esc_html_e('显示缩略图', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_meta')); ?>" value="1" <?php checked(!isset($instance['show_meta']) || !empty($instance['show_meta'])); ?>> <?php esc_html_e('显示 meta', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_number')); ?>" value="1" <?php checked(!empty($instance['show_number'])); ?>> <?php esc_html_e('显示序号', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$style = sanitize_key((string) ($new_instance['style'] ?? 'list'));
if (!in_array($style, ['list', 'card', 'mini'], true)) {
$style = 'list';
}
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'modified', 'views', 'comments', 'likes', 'favorites', 'downloads', 'rand'], true)) {
$orderby = 'date';
}
$paginate = sanitize_key((string) ($new_instance['paginate'] ?? 'none'));
if (!in_array($paginate, ['none', 'ajax', 'number'], true)) {
$paginate = 'none';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(30, (int) ($new_instance['count'] ?? 6))),
'style' => $style,
'orderby' => $orderby,
'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
'pay_type' => sanitize_key((string) ($new_instance['pay_type'] ?? '')),
'paginate' => $paginate,
'load_mode' => sanitize_key((string) ($new_instance['load_mode'] ?? 'detail')) === 'ajax' ? 'ajax' : 'detail',
'show_thumb' => !empty($new_instance['show_thumb']) ? 1 : 0,
'show_meta' => !empty($new_instance['show_meta']) ? 1 : 0,
'show_number' => !empty($new_instance['show_number']) ? 1 : 0,
];
}
}
-287
View File
@@ -1,287 +0,0 @@
<?php
/**
* Horizontal one-line posts scroller (zibll zib_widget_ui_oneline_posts).
*
* Uses shared post renderer + slider.js Type A (.swiper-scroll).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetOnelinePosts extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_oneline_posts',
esc_html__('AKSRT 横向文章', 'aksrt'),
[
'description' => esc_html__('横向滚动的文章卡片/迷你列表(共享 swiper-scroll)。', 'aksrt'),
'classname' => 'widget_aksrt_oneline_posts',
]
);
}
/**
* @param array<string,mixed> $instance
* @return array<string,mixed>
*/
public static function build_query_args(array $instance): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$cat = (int) ($instance['cat'] ?? 0);
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
$order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC';
$query = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
];
if ($cat > 0) {
$query['cat'] = $cat;
}
if ($limit_day > 0) {
$query['date_query'] = [['after' => $limit_day . ' days ago']];
}
switch ($orderby) {
case 'views':
$query['meta_key'] = 'aksrt_post_views';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'comments':
$query['orderby'] = 'comment_count';
$query['order'] = $order;
break;
case 'likes':
$query['meta_key'] = '_aksrt_like_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'favorites':
$query['meta_key'] = '_aksrt_favorite_count';
$query['orderby'] = 'meta_value_num';
$query['order'] = $order;
break;
case 'modified':
$query['orderby'] = 'modified';
$query['order'] = $order;
break;
case 'rand':
$query['orderby'] = 'rand';
break;
default:
$query['orderby'] = 'date';
$query['order'] = $order;
}
$pay_type = sanitize_key((string) ($instance['pay_type'] ?? ''));
if ($pay_type !== '' && $pay_type !== '0' && $pay_type !== 'no') {
if ($pay_type === 'any' || $pay_type === 'paid') {
$query['meta_query'] = [
'relation' => 'OR',
['key' => '_aksrt_pay_price', 'value' => 0, 'compare' => '>', 'type' => 'NUMERIC'],
['key' => '_aksrt_pay_type', 'value' => ['reading', 'download', 'gallery', 'video', 'vip'], 'compare' => 'IN'],
['key' => '_aksrt_visibility', 'value' => ['pay', 'credits'], 'compare' => 'IN'],
];
} else {
$query['meta_query'] = [['key' => '_aksrt_pay_type', 'value' => $pay_type]];
}
}
return $query;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$style = sanitize_key((string) ($instance['style'] ?? 'card'));
if (!in_array($style, ['card', 'mini', 'list'], true)) {
$style = 'card';
}
$q = new \WP_Query(self::build_query_args($instance));
if (!$q->have_posts()) {
wp_reset_postdata();
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
$mode = $style === 'mini' ? 'mini' : ($style === 'list' ? 'list' : 'card');
$cfg = [
'mode' => $mode,
'context' => 'widget',
'show_thumb' => true,
'show_meta' => $style !== 'mini',
'show_excerpt' => false,
'show_author' => false,
'show_comments' => false,
'show_likes' => false,
'show_tags' => false,
'show_badges' => $style === 'card',
'skip_wrapper' => true,
'extra_class' => 'posts-item',
];
$slides = '';
if (function_exists('aksrt_render_post')) {
while ($q->have_posts()) {
$q->the_post();
$item = aksrt_render_post($cfg);
if ($item === '') {
continue;
}
// Ensure freeMode width calc has a posts-item root when renderer wraps differently.
if (strpos($item, 'posts-item') === false && strpos($item, 'posts-mini') === false) {
$item = '<div class="posts-item">' . $item . '</div>';
}
$slides .= '<div class="swiper-slide ajax-item">' . $item . '</div>';
}
wp_reset_postdata();
} else {
// Fallback: bare thumb+title (should rarely hit).
while ($q->have_posts()) {
$q->the_post();
$thumb = get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail');
$slides .= '<div class="swiper-slide ajax-item"><a class="posts-item aksrt-oneline-fallback" href="' . esc_url(get_permalink()) . '">';
if ($thumb) {
$slides .= '<div class="item-thumbnail"><img src="' . esc_url($thumb) . '" alt="" loading="lazy" /></div>';
}
$slides .= '<div class="item-body"><div class="item-heading">' . esc_html(get_the_title()) . '</div></div></a></div>';
}
wp_reset_postdata();
}
if ($slides === '') {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
// zibll: .widget-main-post.style-{card|mini|list} + .swiper-container.swiper-scroll data-slideClass="posts-item".
ob_start();
echo '<div class="widget-main-post style-' . esc_attr($style) . ' aksrt-oneline-posts ajax-item">';
echo '<div class="swiper-container swiper-scroll aksrt-oneline-swiper" data-slideClass="posts-item">';
echo '<div class="swiper-wrapper">';
echo $slides; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo '<div class="swiper-button-prev" aria-label="' . esc_attr__('上一页', 'aksrt') . '"></div>';
echo '<div class="swiper-button-next" aria-label="' . esc_attr__('下一页', 'aksrt') . '"></div>';
echo '</div></div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 8))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('style')); ?>">
<option value="card" <?php selected(($instance['style'] ?? 'card'), 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
<option value="mini" <?php selected(($instance['style'] ?? ''), 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
<option value="list" <?php selected(($instance['style'] ?? ''), 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="comments" <?php selected(($instance['orderby'] ?? ''), 'comments'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
<option value="likes" <?php selected(($instance['orderby'] ?? ''), 'likes'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
<option value="favorites" <?php selected(($instance['orderby'] ?? ''), 'favorites'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
</select>
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
<option value="desc" <?php selected(($instance['order'] ?? 'desc'), 'desc'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
<option value="asc" <?php selected(($instance['order'] ?? ''), 'asc'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('分类 ID(0=全部)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('cat')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['cat'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('限制最近 N 天(0=不限)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('limit_day')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['limit_day'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('付费类型', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('pay_type')); ?>">
<option value="" <?php selected(($instance['pay_type'] ?? ''), ''); ?>><?php esc_html_e('不限', 'aksrt'); ?></option>
<option value="paid" <?php selected(($instance['pay_type'] ?? ''), 'paid'); ?>><?php esc_html_e('任意付费', 'aksrt'); ?></option>
<option value="reading" <?php selected(($instance['pay_type'] ?? ''), 'reading'); ?>><?php esc_html_e('付费阅读', 'aksrt'); ?></option>
<option value="download" <?php selected(($instance['pay_type'] ?? ''), 'download'); ?>><?php esc_html_e('付费下载', 'aksrt'); ?></option>
</select>
</label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$style = sanitize_key((string) ($new_instance['style'] ?? 'card'));
if (!in_array($style, ['card', 'mini', 'list'], true)) {
$style = 'card';
}
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'modified', 'views', 'comments', 'likes', 'favorites', 'rand'], true)) {
$orderby = 'date';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 8))),
'style' => $style,
'orderby' => $orderby,
'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'cat' => max(0, (int) ($new_instance['cat'] ?? 0)),
'limit_day' => max(0, (int) ($new_instance['limit_day'] ?? 0)),
'pay_type' => sanitize_key((string) ($new_instance['pay_type'] ?? '')),
]; return $business;
}
}
-186
View File
@@ -1,186 +0,0 @@
<?php
/**
* Compact paid-content purchase card for the article sidebar.
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetPostPayBox extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_post_pay_box',
esc_html__('AKSRT 付费购买', 'aksrt'),
[
'description' => esc_html__('仅在文章页 - 侧边栏展示当前文章的紧凑购买卡。', 'aksrt'),
'classname' => 'widget_aksrt_post_pay_box',
]
);
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance = []): string {
if (!is_single() || get_post_type() !== 'post') {
return '';
}
$post = get_queried_object();
if (!($post instanceof \WP_Post) || !function_exists('aksrt_get_pay_meta') || !function_exists('aksrt_is_paid_post') || !aksrt_is_paid_post($post)) {
return '';
}
$meta = aksrt_get_pay_meta($post);
$pay_type = (string) ($meta['pay_type'] ?? 'no');
if ($pay_type === '' || $pay_type === 'no') {
return '';
}
$theme = sanitize_html_class((string) ($instance['theme'] ?? 'jb-red'));
$allowed_themes = ['jb-red', 'jb-yellow', 'jb-blue', 'jb-green', 'jb-purple', 'jb-vip1', 'jb-vip2'];
if (!in_array($theme, $allowed_themes, true)) {
$theme = 'jb-red';
}
$price = function_exists('aksrt_get_user_pay_price')
? aksrt_get_user_pay_price($post)
: [
'amount' => (float) ($meta['price'] ?? 0),
'base' => (float) ($meta['price'] ?? 0),
'original' => (float) ($meta['original_price'] ?? 0),
'modo' => '0',
'currency' => '¥',
];
$amount = max(0.0, (float) ($price['amount'] ?? 0));
$base = max(0.0, (float) ($price['base'] ?? $amount));
$original = max(0.0, (float) ($price['original'] ?? 0));
$pay_modo = (($price['modo'] ?? '0') === 'points') ? 'points' : '0';
$currency = (string) ($price['currency'] ?? ($pay_modo === 'points' ? __('积分', 'aksrt') : '¥'));
$format = static function (float $value) use ($pay_modo, $currency): string {
$number = function_exists('aksrt_format_price') ? aksrt_format_price($value) : number_format($value, 2, '.', '');
return $pay_modo === 'points' ? $number . ' ' . $currency : $currency . $number;
};
$has_access = function_exists('aksrt_has_paid_access') && aksrt_has_paid_access($post);
$is_logged_in = is_user_logged_in();
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink($post));
$demo = trim((string) ($meta['demo_url'] ?? ''));
$sales = 0;
if (function_exists('aksrt_get_download_box_meta')) {
$box = aksrt_get_download_box_meta($post);
$sales = max(0, (int) ($box['sales_count'] ?? 0));
}
$type_labels = [
'reading' => __('付费阅读', 'aksrt'),
'download' => __('付费下载', 'aksrt'),
'gallery' => __('付费图库', 'aksrt'),
'video' => __('付费视频', 'aksrt'),
'vip' => __('会员专属', 'aksrt'),
];
$type_label = $type_labels[$pay_type] ?? __('付费内容', 'aksrt');
$action_labels = [
'reading' => __('查看内容', 'aksrt'),
'download' => __('前往下载', 'aksrt'),
'gallery' => __('查看图片', 'aksrt'),
'video' => __('查看视频', 'aksrt'),
'vip' => __('查看内容', 'aksrt'),
];
$action_label = $action_labels[$pay_type] ?? __('查看内容', 'aksrt');
$show_original = $original > $amount && $original > 0;
$show_member_discount = !$has_access && $amount > 0 && $base > $amount;
ob_start();
?>
<section class="aksrt-post-pay-card <?php echo esc_attr($theme); ?><?php echo $has_access ? ' is-unlocked' : ''; ?>" aria-label="<?php esc_attr_e('付费购买', 'aksrt'); ?>">
<header class="aksrt-post-pay-card__header">
<span class="aksrt-post-pay-card__type"><?php echo esc_html($type_label); ?></span>
<?php if ($sales > 0) : ?>
<span class="aksrt-post-pay-card__sales"><?php echo esc_html(sprintf(__('已售 %s', 'aksrt'), number_format_i18n($sales))); ?></span>
<?php endif; ?>
</header>
<div class="aksrt-post-pay-card__price-row">
<?php if ($has_access) : ?>
<strong class="aksrt-post-pay-card__status"><i class="fa fa-check-circle" aria-hidden="true"></i><?php esc_html_e('已解锁', 'aksrt'); ?></strong>
<?php elseif ($amount > 0) : ?>
<strong class="aksrt-post-pay-card__price"><?php echo esc_html($format($amount)); ?></strong>
<?php if ($show_original) : ?><del><?php echo esc_html($format($original)); ?></del><?php endif; ?>
<?php else : ?>
<strong class="aksrt-post-pay-card__price"><?php esc_html_e('免费', 'aksrt'); ?></strong>
<?php endif; ?>
</div>
<?php if ($show_member_discount) : ?>
<p class="aksrt-post-pay-card__hint"><?php echo esc_html(sprintf(__('已应用会员优惠,原价 %s', 'aksrt'), $format($base))); ?></p>
<?php endif; ?>
<div class="aksrt-post-pay-card__actions">
<?php if ($has_access) : ?>
<a class="but btn-block <?php echo esc_attr($theme); ?>" href="#posts-pay"><i class="fa fa-arrow-down" aria-hidden="true"></i><?php echo esc_html($action_label); ?></a>
<?php elseif (!$is_logged_in) : ?>
<a class="but btn-block jb-blue" href="<?php echo esc_url($login_url); ?>"><i class="fa fa-sign-in" aria-hidden="true"></i><?php esc_html_e('登录后购买', 'aksrt'); ?></a>
<?php if ($amount > 0 && $pay_modo !== 'points' && function_exists('aksrt_allow_guest_content_pay') && aksrt_allow_guest_content_pay($post)) : ?>
<button type="button" class="but btn-block <?php echo esc_attr($theme); ?> cashier-link" data-aksrt-pay-content="<?php echo esc_attr((string) $post->ID); ?>"><i class="fa fa-shopping-cart" aria-hidden="true"></i><?php echo esc_html(sprintf(__('游客购买(%s)', 'aksrt'), $format($amount))); ?></button>
<?php endif; ?>
<?php else : ?>
<button type="button" class="but btn-block <?php echo esc_attr($theme); ?> cashier-link" data-aksrt-pay-content="<?php echo esc_attr((string) $post->ID); ?>"><i class="fa fa-shopping-cart" aria-hidden="true"></i><?php echo esc_html(sprintf(__('立即购买(%s)', 'aksrt'), $format($amount))); ?></button>
<?php endif; ?>
<?php if ($demo !== '') : ?>
<a class="but hollow btn-block" href="<?php echo esc_url($demo); ?>" target="_blank" rel="noopener noreferrer"><i class="fa fa-external-link" aria-hidden="true"></i><?php esc_html_e('演示地址', 'aksrt'); ?></a>
<?php endif; ?>
</div>
</section>
<?php
return (string) ob_get_clean();
}
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$theme = sanitize_html_class((string) ($instance['theme'] ?? 'jb-red'));
$options = [
'jb-red' => __('红', 'aksrt'),
'jb-yellow' => __('橙', 'aksrt'),
'jb-blue' => __('蓝', 'aksrt'),
'jb-green' => __('绿', 'aksrt'),
'jb-purple' => __('紫', 'aksrt'),
'jb-vip1' => __('会员金', 'aksrt'),
'jb-vip2' => __('会员灰', 'aksrt'),
];
?>
<p class="description"><?php esc_html_e('仅在文章页 - 侧边栏显示付费文章的紧凑购买卡。', 'aksrt'); ?></p>
<p>
<label><?php esc_html_e('色彩主题', 'aksrt'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('theme')); ?>">
<?php foreach ($options as $value => $label) : ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($theme, $value); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$theme = sanitize_html_class((string) ($new_instance['theme'] ?? 'jb-red'));
$allowed = ['jb-red', 'jb-yellow', 'jb-blue', 'jb-green', 'jb-purple', 'jb-vip1', 'jb-vip2'];
if (!in_array($theme, $allowed, true)) {
$theme = 'jb-red';
}
return ['theme' => $theme];
}
}
-292
View File
@@ -1,292 +0,0 @@
<?php
/**
* Simple image slider widget (zibll widget_ui_slider → aksrt_render_slider / new-swiper).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetSlider extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_slider',
esc_html__('AKSRT 幻灯片', 'aksrt'),
[
'description' => esc_html__('多图轮播(共享 new-swiper / Swiper 栈),支持链接与标题。', 'aksrt'),
'classname' => 'widget_aksrt_slider slide-widget',
]
);
}
public function widget($args, $instance): void {
$slides = $this->normalize_slides($instance['slides'] ?? []);
if (!$slides) {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('当前配置下没有可显示的内容', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<div class="c-red muted-box mb20"><b>' . esc_html__('幻灯片模块:', 'aksrt') . '</b>' . esc_html__('当前配置下没有可显示的内容,请正确配置或移出该模块', 'aksrt') . '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$interval = max(0, min(20, (int) ($instance['interval'] ?? 4)));
$loop = !isset($instance['loop']) || !empty($instance['loop']);
$dots = !isset($instance['pagination']) || !empty($instance['pagination']);
$nav = !isset($instance['button']) || !empty($instance['button']);
$blank = !empty($instance['blank']);
$height = max(80, min(800, (int) ($instance['height'] ?? $instance['pc_height'] ?? 400)));
$m_height = max(60, min(800, (int) ($instance['m_height'] ?? $height)));
$effect = sanitize_key((string) ($instance['effect'] ?? 'slide'));
if (!in_array($effect, ['slide', 'fade', 'cube', 'coverflow', 'flip'], true)) {
$effect = 'slide';
}
// Zibll CFS_Module::slide() options parity.
$direction = sanitize_key((string) ($instance['direction'] ?? 'horizontal'));
if (!in_array($direction, ['horizontal', 'vertical'], true)) {
$direction = 'horizontal';
}
$autoplay = !isset($instance['autoplay']) || !empty($instance['autoplay']);
$speed = max(0, min(3000, (int) ($instance['speed'] ?? 0)));
$spacebetween= max(0, min(500, (int) ($instance['spacebetween'] ?? 15)));
$scale_height= !empty($instance['scale_height']) || !isset($instance['scale_height']);
$scale = max(10, min(300, (int) ($instance['scale'] ?? 35)));
$slide_payload = [];
foreach ($slides as $slide) {
$slide_payload[] = [
'image' => $slide['image'],
'link' => $slide['href'],
'title' => $slide['title'],
'desc' => $slide['desc'],
'alt' => $slide['title'],
'target' => $blank ? '_blank' : '_self',
];
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if (function_exists('aksrt_render_slider')) {
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo aksrt_render_slider([
'slides' => $slide_payload,
'effect' => $effect,
'loop' => $loop,
'autoplay' => $autoplay,
'interval' => $interval * 1000,
'speed' => $speed,
'direction' => $direction,
'spaceBetween' => $spacebetween,
'pc_height' => $height . 'px',
'm_height' => $m_height . 'px',
'scale_height' => $scale_height && $direction !== 'vertical',
'scale_ratio' => $scale / 100,
'show_nav' => $nav,
'show_pagination' => $dots,
'slider_class' => 'slide-widget',
'wrapper_class' => 'aksrt-widget-slider-wrap',
]);
} else {
// Minimal fallback without helper.
echo '<div class="aksrt-widget-slider" style="--aksrt-slider-h:' . (int) $height . 'px">';
foreach ($slide_payload as $i => $slide) {
$active = $i === 0 ? ' is-active' : '';
echo '<div class="aksrt-widget-slider__slide' . esc_attr($active) . '">';
$img = '<img src="' . esc_url($slide['image']) . '" alt="' . esc_attr($slide['title']) . '" loading="lazy" />';
if ($slide['link'] !== '') {
echo '<a href="' . esc_url($slide['link']) . '"' . ($blank ? ' target="_blank" rel="noopener noreferrer"' : '') . '>' . $img . '</a>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo $img; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div>';
}
echo '</div>';
}
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function form($instance): void {
$title = (string) ($instance['title'] ?? '');
$interval = (int) ($instance['interval'] ?? 4);
$height = (int) ($instance['height'] ?? $instance['pc_height'] ?? 400);
$m_height = (int) ($instance['m_height'] ?? $height);
$effect = sanitize_key((string) ($instance['effect'] ?? 'slide'));
$direction = sanitize_key((string) ($instance['direction'] ?? 'horizontal'));
$speed = (int) ($instance['speed'] ?? 0);
$spacebetween = (int) ($instance['spacebetween'] ?? 15);
$scale = (int) ($instance['scale'] ?? 35);
$slides = $this->normalize_slides($instance['slides'] ?? []);
if (!$slides) {
$slides = [
['image' => '', 'href' => '', 'title' => '', 'desc' => ''],
['image' => '', 'href' => '', 'title' => '', 'desc' => ''],
];
}
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr($title); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('幻灯片方向', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('direction')); ?>">
<option value="horizontal" <?php selected($direction, 'horizontal'); ?>><?php esc_html_e('左右切换', 'aksrt'); ?></option>
<option value="vertical" <?php selected($direction, 'vertical'); ?>><?php esc_html_e('上下切换', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('切换效果', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('effect')); ?>">
<option value="slide" <?php selected($effect, 'slide'); ?>><?php esc_html_e('滑动', 'aksrt'); ?></option>
<option value="fade" <?php selected($effect, 'fade'); ?>><?php esc_html_e('淡入', 'aksrt'); ?></option>
<option value="coverflow" <?php selected($effect, 'coverflow'); ?>><?php esc_html_e('Coverflow', 'aksrt'); ?></option>
<option value="cube" <?php selected($effect, 'cube'); ?>><?php esc_html_e('Cube', 'aksrt'); ?></option>
<option value="flip" <?php selected($effect, 'flip'); ?>><?php esc_html_e('Flip', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('PC 高度(px)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="80" max="800" name="<?php echo esc_attr($this->get_field_name('height')); ?>" value="<?php echo esc_attr((string) $height); ?>">
</label>
<label><?php esc_html_e('移动端高度(px)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="60" max="800" name="<?php echo esc_attr($this->get_field_name('m_height')); ?>" value="<?php echo esc_attr((string) $m_height); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('scale_height')); ?>" value="1" <?php checked(!isset($instance['scale_height']) || !empty($instance['scale_height'])); ?>> <?php esc_html_e('按比例自动高度', 'aksrt'); ?></label>
<label><?php esc_html_e('长宽比例 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="10" max="300" step="5" name="<?php echo esc_attr($this->get_field_name('scale')); ?>" value="<?php echo esc_attr((string) $scale); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('间距(px)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="500" step="5" name="<?php echo esc_attr($this->get_field_name('spacebetween')); ?>" value="<?php echo esc_attr((string) $spacebetween); ?>">
</label>
<label><?php esc_html_e('切换速度(ms,0=自动)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="3000" step="100" name="<?php echo esc_attr($this->get_field_name('speed')); ?>" value="<?php echo esc_attr((string) $speed); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('停顿时间(秒)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="20" name="<?php echo esc_attr($this->get_field_name('interval')); ?>" value="<?php echo esc_attr((string) $interval); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('loop')); ?>" value="1" <?php checked(!empty($instance['loop']) || !isset($instance['loop'])); ?>> <?php esc_html_e('循环播放', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('button')); ?>" value="1" <?php checked(!empty($instance['button']) || !isset($instance['button'])); ?>> <?php esc_html_e('翻页按钮', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('pagination')); ?>" value="1" <?php checked(!empty($instance['pagination']) || !isset($instance['pagination'])); ?>> <?php esc_html_e('指示点', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('autoplay')); ?>" value="1" <?php checked(!isset($instance['autoplay']) || !empty($instance['autoplay'])); ?>> <?php esc_html_e('自动播放', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blank')); ?>" value="1" <?php checked(!empty($instance['blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php foreach ($slides as $i => $slide) : ?>
<p style="border-top:1px solid #ddd;padding-top:8px;">
<strong><?php echo esc_html(sprintf(__('幻灯片 %d', 'aksrt'), $i + 1)); ?></strong><br>
<label><?php esc_html_e('图片 URL', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][image]"); ?>" value="<?php echo esc_attr($slide['image']); ?>">
</label>
<label><?php esc_html_e('链接', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][href]"); ?>" value="<?php echo esc_attr($slide['href']); ?>">
</label>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][title]"); ?>" value="<?php echo esc_attr($slide['title']); ?>">
</label>
<label><?php esc_html_e('简介', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][desc]"); ?>" value="<?php echo esc_attr($slide['desc']); ?>">
</label>
</p>
<?php endforeach; ?>
<?php
$extra = max(0, 4 - count($slides));
for ($j = 0; $j < $extra; $j++) {
$i = count($slides) + $j;
?>
<p style="border-top:1px dashed #eee;padding-top:8px;">
<strong><?php echo esc_html(sprintf(__('空槽 %d', 'aksrt'), $i + 1)); ?></strong>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][image]"); ?>" placeholder="<?php esc_attr_e('图片 URL', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][href]"); ?>" placeholder="<?php esc_attr_e('链接', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][title]"); ?>" placeholder="<?php esc_attr_e('标题', 'aksrt'); ?>">
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('slides') . "[{$i}][desc]"); ?>" placeholder="<?php esc_attr_e('简介', 'aksrt'); ?>">
</p>
<?php
}
?>
<p class="description"><?php esc_html_e('使用主题共享 Swiper(.new-swiper)。图片 URL 必填;仅 1 张时不滑动。', 'aksrt'); ?></p>
<p class="description"><?php esc_html_e('对齐 Zibll 幻灯片:方向/效果/比例自动高度/间距/速度/自动播放/间隔。视频背景与图层视差由共享 Swiper 栈支持。', 'aksrt'); ?></p>
<?php
}
public function update($new_instance, $old_instance): array {
$slides = $this->normalize_slides($new_instance['slides'] ?? []);
$effect = sanitize_key((string) ($new_instance['effect'] ?? 'slide'));
if (!in_array($effect, ['slide', 'fade', 'cube', 'coverflow', 'flip'], true)) {
$effect = 'slide';
}
$direction = sanitize_key((string) ($new_instance['direction'] ?? 'horizontal'));
if (!in_array($direction, ['horizontal', 'vertical'], true)) {
$direction = 'horizontal';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'interval' => max(0, min(20, (int) ($new_instance['interval'] ?? 4))),
'height' => max(80, min(800, (int) ($new_instance['height'] ?? $new_instance['pc_height'] ?? 400))),
'm_height' => max(60, min(800, (int) ($new_instance['m_height'] ?? ($new_instance['height'] ?? $new_instance['pc_height'] ?? 400)))),
'effect' => $effect,
'direction' => $direction,
'speed' => max(0, min(3000, (int) ($new_instance['speed'] ?? 0))),
'spacebetween'=> max(0, min(500, (int) ($new_instance['spacebetween'] ?? 15))),
'scale_height'=> !empty($new_instance['scale_height']) ? 1 : 0,
'scale' => max(10, min(300, (int) ($new_instance['scale'] ?? 35))),
'loop' => !empty($new_instance['loop']) ? 1 : 0,
'button' => !empty($new_instance['button']) ? 1 : 0,
'pagination' => !empty($new_instance['pagination']) ? 1 : 0,
'autoplay' => !empty($new_instance['autoplay']) ? 1 : 0,
'blank' => !empty($new_instance['blank']) ? 1 : 0,
'slides' => $slides,
]; return $business;
}
/**
* @param mixed $raw
* @return array<int, array{image:string,href:string,title:string,desc:string}>
*/
private function normalize_slides($raw): array {
if (!is_array($raw)) {
return [];
}
$out = [];
foreach ($raw as $row) {
if (!is_array($row)) {
continue;
}
$image = esc_url_raw(trim((string) ($row['image'] ?? $row['link'] ?? '')));
if ($image === '') {
continue;
}
$out[] = [
'image' => $image,
'href' => esc_url_raw(trim((string) ($row['href'] ?? ''))),
'title' => sanitize_text_field((string) ($row['title'] ?? '')),
'desc' => sanitize_text_field((string) ($row['desc'] ?? $row['dec'] ?? '')),
];
if (count($out) >= 8) {
break;
}
}
return $out;
}
}
-354
View File
@@ -1,354 +0,0 @@
<?php
/**
* Term aggregation cards (zibll zib_widget_ui_term_lists_card → zib_term_aggregation).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetTermAggregation extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_term_aggregation',
esc_html__('AKSRT 分类聚合', 'aksrt'),
[
'description' => esc_html__('分类封面 + 描述 + 更多文章 CTA + 文章列表聚合卡。', 'aksrt'),
'classname' => 'widget_aksrt_term_aggregation',
]
);
}
/** @return array<int,int> */
public static function parse_ids_static(string $raw): array {
if ($raw === '') {
return [];
}
$ids = [];
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
$id = (int) $p;
if ($id > 0) {
$ids[] = $id;
}
}
return array_values(array_unique($ids));
}
/**
* @param \WP_Term $term
*/
private static function term_cover_url(\WP_Term $term): string {
if (class_exists(WidgetTermCards::class) && method_exists(WidgetTermCards::class, 'term_cover_url')) {
return WidgetTermCards::term_cover_url($term);
}
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$url = wp_get_attachment_image_url($img_id, 'medium');
if ($url) {
return (string) $url;
}
}
$url = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
return $url !== '' ? $url : get_template_directory_uri() . '/assets/img/placeholder.svg';
}
/**
* Sum of post views under a term (best-effort).
*/
private static function term_views_sum(int $term_id, string $taxonomy): int {
$q = new \WP_Query([
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 50,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => true,
'update_post_term_cache' => false,
'tax_query' => [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $term_id,
]],
]);
$sum = 0;
foreach ($q->posts as $pid) {
$sum += function_exists('aksrt_get_post_views')
? (int) aksrt_get_post_views((int) $pid)
: (int) get_post_meta((int) $pid, 'aksrt_post_views', true);
}
return $sum;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'category';
}
$term_ids = self::parse_ids_static((string) ($instance['term_ids'] ?? ''));
$count = max(1, min(20, (int) ($instance['count'] ?? 4)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$order = strtoupper(sanitize_key((string) ($instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC';
$columns = max(1, min(3, (int) ($instance['columns'] ?? 1)));
$target_blank = !empty($instance['target_blank']);
$tgt = $target_blank ? ' target="_blank" rel="noopener noreferrer"' : '';
if (!$term_ids) {
$terms = get_terms([
'taxonomy' => $taxonomy,
'number' => max(1, min(4, (int) ($instance['auto_terms'] ?? 2))),
'orderby' => 'count',
'order' => 'DESC',
'hide_empty' => true,
]);
if (is_wp_error($terms) || !$terms) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
$term_ids = array_map(static fn($t) => (int) $t->term_id, $terms);
}
ob_start();
echo '<div class="aksrt-term-agg ajax-item" style="--aksrt-ta-cols:' . esc_attr((string) $columns) . '">';
foreach ($term_ids as $term_id) {
$term = get_term($term_id, $taxonomy);
if (!$term || is_wp_error($term)) {
continue;
}
$link = get_term_link($term);
if (is_wp_error($link)) {
continue;
}
$cover = self::term_cover_url($term);
$views = self::term_views_sum((int) $term->term_id, $taxonomy);
$more_badge = '<badge class="img-badge px12"><i class="fa fa-fire" aria-hidden="true"></i> ' . esc_html((string) $views) . '</badge>';
$img_html = '';
if (function_exists('aksrt_graphic_card')) {
$img_html = aksrt_graphic_card([
'type' => '',
'class' => '',
'img' => $cover,
'alt' => $term->name,
'link' => [
'url' => $link,
'target' => $target_blank ? '_blank' : '',
],
'lazy' => true,
'height_scale' => 70,
'mask_opacity' => 0,
'more' => $more_badge,
]);
}
$description = trim((string) $term->description);
if ($description === '' && current_user_can('manage_categories')) {
$edit = get_edit_term_link((int) $term->term_id, $taxonomy);
$description = esc_html__('请在后台为该分类添加描述。', 'aksrt');
if ($edit) {
$description .= ' <a href="' . esc_url($edit) . '">' . esc_html__('立即编辑', 'aksrt') . '</a>';
}
} else {
$description = esc_html($description);
}
$posts_q = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => false,
'tax_query' => [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => (int) $term->term_id,
]],
];
if ($orderby === 'views') {
$posts_q['meta_key'] = 'aksrt_post_views';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = $order;
} elseif ($orderby === 'like') {
$posts_q['meta_key'] = '_aksrt_like_count';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = $order;
} elseif ($orderby === 'favorite') {
$posts_q['meta_key'] = '_aksrt_favorite_count';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = $order;
} elseif ($orderby === 'comment_count') {
$posts_q['orderby'] = 'comment_count';
$posts_q['order'] = $order;
} elseif ($orderby === 'modified') {
$posts_q['orderby'] = 'modified';
$posts_q['order'] = $order;
} elseif ($orderby === 'rand') {
$posts_q['orderby'] = 'rand';
} else {
$posts_q['orderby'] = 'date';
$posts_q['order'] = $order;
}
$q = new \WP_Query($posts_q);
$found = (int) $q->found_posts;
echo '<div class="zib-widget term-aggregation mb10 aksrt-term-agg__card">';
echo '<div class="mb20 hover-zoom-img flex px12-sm">';
echo '<div class="term-img flex0 em09-sm">' . $img_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="term-title ml10 flex xx flex1 jsb">';
echo '<a class="em14 key-color text-ellipsis"' . $tgt . ' href="' . esc_url($link) . '">' . esc_html($term->name) . '</a>';
echo '<div class="text-ellipsis-2 muted-color">' . $description . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="term-meta flex jsb ac">';
echo '<span class="px12 muted-3-color text-ellipsis"><i class="fa fa-file-text-o fa-fw" aria-hidden="true"></i>'
. esc_html(sprintf(
/* translators: %s: article count */
__('%s篇文章', 'aksrt'),
(string) $found
))
. '</span>';
echo '<a class="but p2-10 px12 c-blue hide-sm shrink0"' . $tgt . ' href="' . esc_url($link) . '"><i class="fa fa-angle-right" aria-hidden="true"></i>' . esc_html__('更多文章', 'aksrt') . '</a>';
echo '</div></div></div>';
if ($q->have_posts()) {
while ($q->have_posts()) {
$q->the_post();
$pid = get_the_ID();
$title = get_the_title($pid);
$meta = '';
if ($orderby === 'views') {
$v = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($pid) : (int) get_post_meta($pid, 'aksrt_post_views', true);
$meta = '<i class="fa fa-eye mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'like') {
$v = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($pid) : (int) get_post_meta($pid, '_aksrt_like_count', true);
$meta = '<i class="fa fa-heart mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'favorite') {
$v = (int) get_post_meta($pid, '_aksrt_favorite_count', true);
$meta = '<i class="fa fa-star mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'comment_count') {
$meta = '<i class="fa fa-comment-o mr3" aria-hidden="true"></i>' . esc_html((string) get_comments_number($pid));
} else {
$ts = $orderby === 'modified' ? get_the_modified_time('U', $pid) : get_the_time('U', $pid);
$ago = human_time_diff((int) $ts, current_time('timestamp'));
$meta = '<i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html($ago);
}
echo '<div class="mt10 flex jsb ac">';
echo '<div class="text-ellipsis"><a class="icon-circle mln3"' . $tgt . ' href="' . esc_url(get_permalink($pid)) . '">' . esc_html($title) . '</a></div>';
echo '<div class="em09 muted-3-color flex0 ml10">' . $meta . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
wp_reset_postdata();
}
echo '</div>';
}
echo '</div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$taxs = get_taxonomies(['public' => true], 'objects');
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('分类法', 'aksrt'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('taxonomy')); ?>">
<?php foreach ($taxs as $tax) : ?>
<option value="<?php echo esc_attr($tax->name); ?>" <?php selected(($instance['taxonomy'] ?? 'category'), $tax->name); ?>><?php echo esc_html($tax->labels->name); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('term ID(逗号,空=自动热门)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('term_ids')); ?>" value="<?php echo esc_attr((string) ($instance['term_ids'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('每分类文章数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="20" name="<?php echo esc_attr($this->get_field_name('count')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['count'] ?? 4))); ?>">
</label>
<label><?php esc_html_e('列数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 1))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('文章排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="like" <?php selected(($instance['orderby'] ?? ''), 'like'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
<option value="favorite" <?php selected(($instance['orderby'] ?? ''), 'favorite'); ?>><?php esc_html_e('收藏', 'aksrt'); ?></option>
<option value="comment_count" <?php selected(($instance['orderby'] ?? ''), 'comment_count'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
<option value="rand" <?php selected(($instance['orderby'] ?? ''), 'rand'); ?>><?php esc_html_e('随机', 'aksrt'); ?></option>
</select>
<select name="<?php echo esc_attr($this->get_field_name('order')); ?>">
<option value="desc" <?php selected(($instance['order'] ?? 'desc'), 'desc'); ?>><?php esc_html_e('降序', 'aksrt'); ?></option>
<option value="asc" <?php selected(($instance['order'] ?? ''), 'asc'); ?>><?php esc_html_e('升序', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'modified', 'views', 'like', 'favorite', 'comment_count', 'rand'], true)) {
$orderby = 'date';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'term_ids' => sanitize_text_field((string) ($new_instance['term_ids'] ?? '')),
'count' => max(1, min(20, (int) ($new_instance['count'] ?? 4))),
'columns' => max(1, min(3, (int) ($new_instance['columns'] ?? 1))),
'orderby' => $orderby,
'order' => strtoupper(sanitize_key((string) ($new_instance['order'] ?? 'desc'))) === 'ASC' ? 'ASC' : 'DESC',
'auto_terms' => max(1, min(4, (int) ($new_instance['auto_terms'] ?? 2))),
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
]; return $business;
}
}
-304
View File
@@ -1,304 +0,0 @@
<?php
/**
* Taxonomy term graphic cards (zibll zib_widget_ui_term_card → zib_graphic_card).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetTermCards extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_term_cards',
esc_html__('AKSRT 分类/标签卡片', 'aksrt'),
[
'description' => esc_html__('以图文封面卡片展示分类或标签(style-1..4)。', 'aksrt'),
'classname' => 'widget_aksrt_term_cards',
]
);
}
/** @return array<int,int> */
public static function parse_ids_static(string $raw): array {
if ($raw === '') {
return [];
}
$ids = [];
foreach (preg_split('/[\s,]+/', $raw) ?: [] as $p) {
$id = (int) $p;
if ($id > 0) {
$ids[] = $id;
}
}
return array_values(array_unique($ids));
}
/**
* Resolve term cover URL.
*/
public static function term_cover_url(\WP_Term $term): string {
$thumb = '';
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$thumb = (string) (wp_get_attachment_image_url($img_id, 'medium_large') ?: wp_get_attachment_image_url($img_id, 'medium') ?: '');
}
if ($thumb === '') {
$thumb = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
}
if ($thumb === '') {
// Lightweight default gradient placeholder as data URI is heavy — use theme placeholder.
$thumb = get_template_directory_uri() . '/assets/img/placeholder.svg';
}
return $thumb;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'category';
}
$number = max(1, min(30, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
$type = sanitize_key((string) ($instance['type'] ?? 'style-1'));
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
$type = 'style-1';
}
$height_scale = max(20, min(300, (int) ($instance['height_scale'] ?? 60)));
$mask_opacity = max(0, min(90, (int) ($instance['mask_opacity'] ?? 10)));
$pc_row = max(1, min(12, (int) ($instance['pc_row'] ?? $instance['columns'] ?? 2)));
$m_row = max(1, min(12, (int) ($instance['m_row'] ?? 1)));
$target_blank = !empty($instance['target_blank']);
$term_args = [
'taxonomy' => $taxonomy,
'number' => $number,
'hide_empty' => $hide_empty,
];
if ($include) {
$term_args['include'] = $include;
$term_args['orderby'] = 'include';
} else {
$term_args['orderby'] = in_array($orderby, ['name', 'count', 'id'], true) ? $orderby : 'count';
$term_args['order'] = $orderby === 'name' ? 'ASC' : 'DESC';
}
$terms = get_terms($term_args);
if (is_wp_error($terms) || !$terms) {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无分类', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
$icon = $taxonomy === 'category'
? '<i class="fa fa-folder-open-o mr6" aria-hidden="true"></i>'
: '<i class="fa fa-cube mr6" aria-hidden="true"></i>';
$is_row = count($terms) > 1;
// Bootstrap-like col classes already used elsewhere; keep numeric CSS vars as fallback.
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
ob_start();
echo '<div class="aksrt-term-cards ajax-item' . ($is_row ? ' mb10' : ' mb20') . '">';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($terms as $term) {
if (!($term instanceof \WP_Term)) {
continue;
}
$link = get_term_link($term);
if (is_wp_error($link)) {
continue;
}
$name = mb_strlen($term->name) > 16 ? mb_substr($term->name, 0, 16) . '…' : $term->name;
$desc = trim((string) $term->description);
if (mb_strlen($desc) > 24) {
$desc = mb_substr($desc, 0, 24) . '…';
}
$count = (int) $term->count;
$count_label = sprintf(
/* translators: %d: post count */
_n('%d篇文章', '%d篇文章', $count, 'aksrt'),
$count
);
$text1 = $name;
$text2 = $desc;
$text3 = $icon . esc_html($count_label);
if ($type === 'style-2' || $type === 'style-4') {
$text3 = '<item data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%d篇文章', 'aksrt'), $count)) . '">' . $icon . $count . '</item>';
}
if ($type === 'style-3' || $type === 'style-4') {
$text1 = $icon . esc_html($name);
if ($type === 'style-3') {
$text3 = '<i class="fa mr6 fa-file-text-o" aria-hidden="true"></i>' . esc_html($count_label);
}
}
$card_args = [
'type' => $type,
'class' => 'mb10',
'img' => self::term_cover_url($term),
'alt' => $name . ($desc !== '' ? '-' . $desc : ''),
'link' => [
'url' => $link,
'target' => $target_blank ? '_blank' : '',
],
'text1' => $text1,
'text2' => $text2,
'text3' => $text3,
'lazy' => true,
'height_scale' => $height_scale,
'mask_opacity' => $mask_opacity,
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">';
}
if (function_exists('aksrt_graphic_card')) {
echo aksrt_graphic_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '<a class="aksrt-term-card" href="' . esc_url($link) . '"><div class="aksrt-term-card__name">' . esc_html($name) . '</div></a>';
}
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$taxs = get_taxonomies(['public' => true], 'objects');
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('分类法', 'aksrt'); ?>
<select class="widefat" name="<?php echo esc_attr($this->get_field_name('taxonomy')); ?>">
<?php foreach ($taxs as $tax) : ?>
<option value="<?php echo esc_attr($tax->name); ?>" <?php selected(($instance['taxonomy'] ?? 'category'), $tax->name); ?>><?php echo esc_html($tax->labels->name . ' (' . $tax->name . ')'); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="30" name="<?php echo esc_attr($this->get_field_name('number')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['number'] ?? 8))); ?>">
</label>
<label><?php esc_html_e('PC 列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="12" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? $instance['columns'] ?? 2))); ?>">
</label>
<label><?php esc_html_e('移动列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="12" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('卡片样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('type')); ?>">
<option value="style-1" <?php selected(($instance['type'] ?? 'style-1'), 'style-1'); ?>><?php esc_html_e('样式 1(左下标题)', 'aksrt'); ?></option>
<option value="style-2" <?php selected(($instance['type'] ?? ''), 'style-2'); ?>><?php esc_html_e('样式 2(居中)', 'aksrt'); ?></option>
<option value="style-3" <?php selected(($instance['type'] ?? ''), 'style-3'); ?>><?php esc_html_e('样式 3(左下多层)', 'aksrt'); ?></option>
<option value="style-4" <?php selected(($instance['type'] ?? ''), 'style-4'); ?>><?php esc_html_e('样式 4(图+下方文案)', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="20" max="300" name="<?php echo esc_attr($this->get_field_name('height_scale')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['height_scale'] ?? 60))); ?>">
</label>
<label><?php esc_html_e('遮罩 0-90', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="90" name="<?php echo esc_attr($this->get_field_name('mask_opacity')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mask_opacity'] ?? 10))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('排序', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('orderby')); ?>">
<option value="count" <?php selected(($instance['orderby'] ?? 'count'), 'count'); ?>><?php esc_html_e('文章数', 'aksrt'); ?></option>
<option value="name" <?php selected(($instance['orderby'] ?? ''), 'name'); ?>><?php esc_html_e('名称', 'aksrt'); ?></option>
<option value="id" <?php selected(($instance['orderby'] ?? ''), 'id'); ?>><?php esc_html_e('ID', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('包含 term ID(逗号)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('include')); ?>" value="<?php echo esc_attr((string) ($instance['include'] ?? '')); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!isset($instance['hide_empty']) || !empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空分类', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$type = sanitize_key((string) ($new_instance['type'] ?? 'style-1'));
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
$type = 'style-1';
}
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'number' => max(1, min(30, (int) ($new_instance['number'] ?? 8))),
'pc_row' => max(1, min(12, (int) ($new_instance['pc_row'] ?? 2))),
'm_row' => max(1, min(12, (int) ($new_instance['m_row'] ?? 1))),
'columns' => max(1, min(12, (int) ($new_instance['pc_row'] ?? $new_instance['columns'] ?? 2))),
'type' => $type,
'height_scale' => max(20, min(300, (int) ($new_instance['height_scale'] ?? 60))),
'mask_opacity' => max(0, min(90, (int) ($new_instance['mask_opacity'] ?? 10))),
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'id'], true) ? $new_instance['orderby'] : 'count',
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
]; return $business;
}
}
-166
View File
@@ -1,166 +0,0 @@
<?php
/**
* Text title / section heading (zibll zib_widget_ui_text_title).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetTextTitle extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_text_title',
esc_html__('AKSRT 文字标题', 'aksrt'),
[
'description' => esc_html__('自定义标题 + 副标题区块。', 'aksrt'),
'classname' => 'widget_aksrt_text_title',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$title = trim((string) ($instance['title'] ?? ''));
$desc = trim((string) ($instance['desc'] ?? ''));
if ($title === '' && $desc === '') {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请填写标题或描述', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<p class="widget-empty">' . esc_html__('请填写标题或描述', 'aksrt') . '</p>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
$align = sanitize_key((string) ($instance['align'] ?? 'center'));
if (!in_array($align, ['left', 'center', 'right'], true)) {
$align = 'center';
}
$icon_left = sanitize_text_field((string) ($instance['title_icon_left'] ?? ''));
$icon_right = sanitize_text_field((string) ($instance['title_icon_right'] ?? ''));
$title_size = max(12, min(48, (int) ($instance['title_size'] ?? 22)));
$desc_size = max(11, min(24, (int) ($instance['desc_size'] ?? 14)));
$title_color = sanitize_hex_color((string) ($instance['title_color'] ?? '')) ?: '';
$desc_color = sanitize_hex_color((string) ($instance['desc_color'] ?? '')) ?: '';
$icon_width = max(0, min(64, (int) ($instance['title_icon_width'] ?? 0)));
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$style = sprintf(
'--aksrt-tt-title-size:%dpx;--aksrt-tt-desc-size:%dpx;%s%s',
$title_size,
$desc_size,
$title_color !== '' ? '--aksrt-tt-title-color:' . $title_color . ';' : '',
$desc_color !== '' ? '--aksrt-tt-desc-color:' . $desc_color . ';' : ''
);
$icon_w_attr = $icon_width > 0 ? ' style="--icon-width:' . (int) $icon_width . 'px;"' : '';
$left_html = $icon_left !== ''
? '<span class="text-title-icon aksrt-text-title__icon mr6"' . $icon_w_attr . '><i class="' . esc_attr($icon_left) . '" aria-hidden="true"></i></span>'
: '';
$right_html = $icon_right !== ''
? '<span class="text-title-icon aksrt-text-title__icon ml6"' . $icon_w_attr . '><i class="' . esc_attr($icon_right) . '" aria-hidden="true"></i></span>'
: '';
$title_html = $title !== '' ? $left_html . esc_html($title) . $right_html : '';
$desc_html = '';
if ($desc !== '') {
$desc_html = '<div class="aksrt-text-title__desc">' . esc_html($desc) . '</div>';
}
$box = 'aksrt-text-title text-title align-' . esc_attr($align);
echo '<div class="' . esc_attr($box) . '" style="' . esc_attr($style) . '">';
if ($title_html !== '') {
echo '<div class="aksrt-text-title__main">' . $title_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo $desc_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('副标题 / 描述', 'aksrt'); ?>
<textarea class="widefat" rows="2" name="<?php echo esc_attr($this->get_field_name('desc')); ?>"><?php echo esc_textarea((string) ($instance['desc'] ?? '')); ?></textarea>
</label>
</p>
<p>
<label><?php esc_html_e('对齐', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('align')); ?>">
<option value="center" <?php selected(($instance['align'] ?? 'center'), 'center'); ?>><?php esc_html_e('居中', 'aksrt'); ?></option>
<option value="left" <?php selected(($instance['align'] ?? ''), 'left'); ?>><?php esc_html_e('左', 'aksrt'); ?></option>
<option value="right" <?php selected(($instance['align'] ?? ''), 'right'); ?>><?php esc_html_e('右', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('左图标 class', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_icon_left')); ?>" value="<?php echo esc_attr((string) ($instance['title_icon_left'] ?? '')); ?>" placeholder="fas fa-star">
</label>
</p>
<p>
<label><?php esc_html_e('右图标 class', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_icon_right')); ?>" value="<?php echo esc_attr((string) ($instance['title_icon_right'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('标题字号', 'aksrt'); ?>
<input class="tiny-text" type="number" min="12" max="48" name="<?php echo esc_attr($this->get_field_name('title_size')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['title_size'] ?? 22))); ?>">
</label>
<label><?php esc_html_e('描述字号', 'aksrt'); ?>
<input class="tiny-text" type="number" min="11" max="24" name="<?php echo esc_attr($this->get_field_name('desc_size')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['desc_size'] ?? 14))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('标题色 #hex', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title_color')); ?>" value="<?php echo esc_attr((string) ($instance['title_color'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('描述色 #hex', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('desc_color')); ?>" value="<?php echo esc_attr((string) ($instance['desc_color'] ?? '')); ?>">
</label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'desc' => sanitize_text_field((string) ($new_instance['desc'] ?? '')),
'align' => in_array(($new_instance['align'] ?? ''), ['left', 'center', 'right'], true) ? $new_instance['align'] : 'center',
'title_icon_left' => sanitize_text_field((string) ($new_instance['title_icon_left'] ?? '')),
'title_icon_right' => sanitize_text_field((string) ($new_instance['title_icon_right'] ?? '')),
'title_size' => max(12, min(48, (int) ($new_instance['title_size'] ?? 22))),
'desc_size' => max(11, min(24, (int) ($new_instance['desc_size'] ?? 14))),
'title_color' => sanitize_hex_color((string) ($new_instance['title_color'] ?? '')) ?: '',
'desc_color' => sanitize_hex_color((string) ($new_instance['desc_color'] ?? '')) ?: '',
'title_icon_width' => max(0, min(64, (int) ($new_instance['title_icon_width'] ?? 0))),
];
}
}
-324
View File
@@ -1,324 +0,0 @@
<?php
/**
* Current-user / guest identity card (zibll widget_ui_user parity).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetUserCard extends \WP_Widget {
private const DEFAULT_COVER_FILE = 'assets/img/person_background.png';
public function __construct() {
parent::__construct(
'aksrt_user_card',
__('AKSRT 用户卡片', 'aksrt'),
[
'description' => __('未登录显示登录入口;登录后显示当前用户卡片、签到、快捷按钮与更多文章。', 'aksrt'),
'classname' => 'widget_aksrt_user_card',
]
);
}
public function widget($args, $instance): void {
$show_cover = !isset($instance['show_img_bg']) || !empty($instance['show_img_bg']);
$show_stats = !empty($instance['show_stats']) || !isset($instance['show_stats']);
$show_actions = !empty($instance['show_button']) || !isset($instance['show_button']);
$show_checkin = !empty($instance['show_checkin']) || !isset($instance['show_checkin']);
$loged_title = (string) ($instance['loged_title'] ?? __('Hi!请登录', 'aksrt'));
$btn1_text = (string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt'));
$btn2_text = (string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt'));
$btn1_url = (string) ($instance['button_1_url'] ?? '');
$btn2_url = (string) ($instance['button_2_url'] ?? '');
// Zibll button color palettes (button_1_class / button_2_class).
$btn1_class = sanitize_html_class((string) ($instance['button_1_class'] ?? 'jb-pink')) ?: 'jb-pink';
$btn2_class = sanitize_html_class((string) ($instance['button_2_class'] ?? 'jb-blue')) ?: 'jb-blue';
// Fixed user id wins; else current user.
$user_id = !empty($instance['user_id']) ? (int) $instance['user_id'] : 0;
if ($user_id <= 0 && is_user_logged_in()) {
$user_id = get_current_user_id();
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$title = apply_filters('widget_title', (string) ($instance['title'] ?? ''), $instance, $this->id_base);
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($user_id <= 0) {
$this->render_guest($show_cover, $loged_title, $this->get_guest_cover_url($instance));
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
$user = get_userdata($user_id);
if (!$user) {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return;
}
$btn1_url = esc_url($btn1_url !== '' ? $btn1_url : $this->get_default_compose_url());
$btn2_url = esc_url($btn2_url !== '' ? $btn2_url : $this->get_default_center_url());
$is_self = is_user_logged_in() && (int) get_current_user_id() === $user_id;
$avatar_url = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($user_id, ['size' => 144])
: get_avatar_url($user_id, ['size' => 144]);
$profile_url = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($user_id)
: get_author_posts_url($user_id);
$bio = get_user_meta($user_id, 'description', true);
if (!is_string($bio) || $bio === '') {
$bio = (string) $user->description;
}
$cover = (string) get_user_meta($user_id, 'aksrt_background_image', true);
if ($cover === '') {
$cover = $this->get_default_cover_url();
}
$post_count = count_user_posts($user_id, 'post', true);
$follower_count = 0;
$following_count = 0;
if (class_exists('\AKSRT\User\Follow')) {
$follower_count = \AKSRT\User\Follow::get_instance()->get_follower_count($user_id);
$following_count = \AKSRT\User\Follow::get_instance()->get_following_count($user_id);
}
$name_html = function_exists('aksrt_get_user_name')
? aksrt_get_user_name([
'id' => $user_id,
'vip' => true,
'follow' => !$is_self,
'class' => 'aksrt-user-card__name-row',
])
: '<a href="' . esc_url($profile_url) . '">' . esc_html($user->display_name) . '</a>';
$checkin_btn = '';
if ($show_checkin && $is_self && function_exists('aksrt_header_user_checkin_btn')) {
$checkin_btn = aksrt_header_user_checkin_btn($user_id, true);
}
$checkin_html = $checkin_btn !== ''
? '<div class="aksrt-user-card__checkin">' . $checkin_btn . '</div>'
: '';
echo '<div class="aksrt-user-card user-card' . ($show_cover ? ' has-cover' : ' no-cover') . '">';
if ($show_cover) {
echo '<div class="aksrt-user-card__cover" style="background-image:url(' . esc_url($cover) . ')">' . $checkin_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} elseif ($checkin_btn !== '') {
echo '<div class="aksrt-user-card__checkin aksrt-user-card__checkin--without-cover">' . $checkin_btn . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '<div class="aksrt-user-card__body card-content">';
echo '<div class="aksrt-user-card__avatar-wrap">';
echo '<div class="aksrt-user-card__avatar user-avatar">';
$ph = get_template_directory_uri() . '/assets/img/placeholder.svg';
echo '<a href="' . esc_url($profile_url) . '"><img class="aksrt-lazy lazyload" data-src="' . esc_url($avatar_url) . '" src="' . esc_url($ph) . '" alt="' . esc_attr($user->display_name) . '" loading="lazy" width="76" height="76" /></a>';
echo '</div>';
echo '</div>';
echo '<h4 class="aksrt-user-card__name user-name">' . $name_html . '</h4>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($bio !== '') {
echo '<p class="aksrt-user-card__bio user-desc">' . esc_html(wp_trim_words($bio, 24)) . '</p>';
}
if ($show_stats) {
echo '<div class="aksrt-user-card__stats">';
echo '<div class="aksrt-user-card__stat"><span class="aksrt-user-card__stat-value">' . esc_html(number_format_i18n($post_count)) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('文章', 'aksrt') . '</span></div>';
echo '<div class="aksrt-user-card__stat"><span class="aksrt-user-card__stat-value">' . esc_html(number_format_i18n($follower_count)) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('粉丝', 'aksrt') . '</span></div>';
echo '<div class="aksrt-user-card__stat"><span class="aksrt-user-card__stat-value">' . esc_html(number_format_i18n($following_count)) . '</span><span class="aksrt-user-card__stat-label">' . esc_html__('关注', 'aksrt') . '</span></div>';
echo '</div>';
}
if ($show_actions && $is_self) {
echo '<div class="aksrt-user-card__actions user-btns">';
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--primary but ' . esc_attr($btn1_class) . '" href="' . $btn1_url . '">' . esc_html($btn1_text) . '</a>';
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--ghost but ' . esc_attr($btn2_class) . '" href="' . $btn2_url . '">' . esc_html($btn2_text) . '</a>';
echo '</div>';
} elseif ($show_actions) {
echo '<div class="aksrt-user-card__actions"><a class="aksrt-user-card__btn aksrt-user-card__btn--primary" href="' . esc_url($profile_url) . '">' . esc_html__('查看主页', 'aksrt') . '</a></div>';
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function render_guest(bool $show_cover, string $loged_title, string $cover): void {
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
$register = function_exists('aksrt_get_register_url') ? aksrt_get_register_url() : wp_registration_url();
$default_avatar = function_exists('get_avatar_url')
? get_avatar_url(0, ['size' => 144, 'force_default' => true])
: includes_url('images/blank.gif');
$oauth_html = '';
if (class_exists('\AKSRT\Auth\OAuth')) {
$oauth = \AKSRT\Auth\OAuth::get_instance();
if (method_exists($oauth, 'render_social_buttons')) {
$oauth_html = (string) $oauth->render_social_buttons(__('社交账号登录', 'aksrt'));
} elseif (method_exists($oauth, 'get_login_buttons')) {
$oauth_html = (string) $oauth->get_login_buttons();
}
}
echo '<div class="aksrt-user-card user-card is-guest' . ($show_cover ? ' has-cover' : ' no-cover') . '">';
if ($show_cover) {
echo '<div class="aksrt-user-card__cover" style="background-image:url(' . esc_url($cover) . ')"></div>';
}
echo '<div class="aksrt-user-card__body card-content">';
echo '<div class="aksrt-user-card__avatar-wrap"><div class="aksrt-user-card__avatar user-avatar"><img src="' . esc_url($default_avatar) . '" alt="" width="76" height="76" loading="lazy" /></div></div>';
echo '<div class="aksrt-user-card__guest-title">' . esc_html($loged_title) . '</div>';
echo '<div class="aksrt-user-card__actions">';
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--primary signin-loader" href="' . esc_url($login) . '">' . esc_html__('登录', 'aksrt') . '</a>';
if ($register) {
echo '<a class="aksrt-user-card__btn aksrt-user-card__btn--ghost signup-loader" href="' . esc_url($register) . '">' . esc_html__('注册', 'aksrt') . '</a>';
}
echo '</div>';
if ($oauth_html !== '') {
echo '<div class="aksrt-user-card__oauth social_loginbar">' . $oauth_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo '</div></div>';
}
public function form($instance): void {
?>
<p>
<label><?php esc_html_e('标题(可选)', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('固定用户 ID(0=当前用户)', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" name="<?php echo esc_attr($this->get_field_name('user_id')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['user_id'] ?? 0))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('未登录文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('loged_title')); ?>" value="<?php echo esc_attr((string) ($instance['loged_title'] ?? __('Hi!请登录', 'aksrt'))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('未登录封面图 URL(留空使用默认图片)', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('guest_cover')); ?>" value="<?php echo esc_attr((string) ($instance['guest_cover'] ?? '')); ?>" placeholder="<?php echo esc_attr($this->get_default_cover_url()); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('按钮1文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('button_1_text')); ?>" value="<?php echo esc_attr((string) ($instance['button_1_text'] ?? __('发布文章', 'aksrt'))); ?>">
</label>
<label><?php esc_html_e('按钮1跳转链接', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('button_1_url')); ?>" value="<?php echo esc_attr((string) ($instance['button_1_url'] ?? $this->get_default_compose_url())); ?>">
</label>
<label><?php esc_html_e('按钮1颜色', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('button_1_class')); ?>">
<?php foreach (self::palette_opts() as $k => $label) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['button_1_class'] ?? 'jb-pink'), $k); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('按钮2文案', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('button_2_text')); ?>" value="<?php echo esc_attr((string) ($instance['button_2_text'] ?? __('用户中心', 'aksrt'))); ?>">
</label>
<label><?php esc_html_e('按钮2跳转链接', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('button_2_url')); ?>" value="<?php echo esc_attr((string) ($instance['button_2_url'] ?? $this->get_default_center_url())); ?>">
</label>
<label><?php esc_html_e('按钮2颜色', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('button_2_class')); ?>">
<?php foreach (self::palette_opts() as $k => $label) : ?>
<option value="<?php echo esc_attr($k); ?>" <?php selected(($instance['button_2_class'] ?? 'jb-blue'), $k); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_img_bg')); ?>" value="1" <?php checked(!isset($instance['show_img_bg']) || !empty($instance['show_img_bg'])); ?>> <?php esc_html_e('显示封面', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_stats')); ?>" value="1" <?php checked(!isset($instance['show_stats']) || !empty($instance['show_stats'])); ?>> <?php esc_html_e('显示统计', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_button')); ?>" value="1" <?php checked(!isset($instance['show_button']) || !empty($instance['show_button'])); ?>> <?php esc_html_e('显示操作按钮', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('show_checkin')); ?>" value="1" <?php checked(!isset($instance['show_checkin']) || !empty($instance['show_checkin'])); ?>> <?php esc_html_e('显示签到(本人)', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$button_1_url = esc_url_raw(trim((string) ($new_instance['button_1_url'] ?? '')));
$button_2_url = esc_url_raw(trim((string) ($new_instance['button_2_url'] ?? '')));
if ($button_1_url === '') {
$button_1_url = $this->get_default_compose_url();
}
if ($button_2_url === '') {
$button_2_url = $this->get_default_center_url();
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'user_id' => max(0, (int) ($new_instance['user_id'] ?? 0)),
'loged_title' => sanitize_text_field((string) ($new_instance['loged_title'] ?? '')),
'guest_cover' => esc_url_raw(trim((string) ($new_instance['guest_cover'] ?? ''))),
'button_1_text' => sanitize_text_field((string) ($new_instance['button_1_text'] ?? '')),
'button_1_url' => $button_1_url,
'button_1_class'=> self::sanitize_palette((string) ($new_instance['button_1_class'] ?? 'jb-pink'), 'jb-pink'),
'button_2_text' => sanitize_text_field((string) ($new_instance['button_2_text'] ?? '')),
'button_2_url' => $button_2_url,
'button_2_class'=> self::sanitize_palette((string) ($new_instance['button_2_class'] ?? 'jb-blue'), 'jb-blue'),
'show_img_bg' => !empty($new_instance['show_img_bg']) ? 1 : 0,
'show_stats' => !empty($new_instance['show_stats']) ? 1 : 0,
'show_button' => !empty($new_instance['show_button']) ? 1 : 0,
'show_checkin' => !empty($new_instance['show_checkin']) ? 1 : 0,
];
}
private function get_default_center_url(): string {
return function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/');
}
private function get_default_compose_url(): string {
$compose_url = home_url('/');
$pages = get_pages([
'meta_key' => '_wp_page_template',
'meta_value' => 'templates/pages/page-submit.php',
'number' => 1,
]);
if (!empty($pages[0])) {
$compose_url = get_permalink($pages[0]);
}
if ($compose_url === home_url('/') && function_exists('aksrt_get_user_center_url')) {
$compose_url = aksrt_get_user_center_url('posts');
}
return $compose_url;
}
private function get_default_cover_url(): string {
return get_theme_file_uri(self::DEFAULT_COVER_FILE);
}
private function get_guest_cover_url(array $instance): string {
$cover = esc_url_raw(trim((string) ($instance['guest_cover'] ?? '')));
return $cover !== '' ? $cover : $this->get_default_cover_url();
}
/** Zibll CFS_Module::zib_palette() gradient/solid keys (c/b/jb families). */
public static function palette_opts(): array {
return [
'jb-red' => __('红', 'aksrt'),
'jb-yellow' => __('橙', 'aksrt'),
'jb-pink' => __('粉', 'aksrt'),
'jb-cyan' => __('青', 'aksrt'),
'jb-blue' => __('蓝', 'aksrt'),
'jb-green' => __('绿', 'aksrt'),
'jb-purple' => __('紫', 'aksrt'),
'jb-vip1' => __('会员金', 'aksrt'),
'jb-vip2' => __('会员灰', 'aksrt'),
'b-red' => __('红(纯色)', 'aksrt'),
'b-yellow' => __('橙(纯色)', 'aksrt'),
'b-cyan' => __('青(纯色)', 'aksrt'),
'b-blue' => __('蓝(纯色)', 'aksrt'),
'b-green' => __('绿(纯色)', 'aksrt'),
'b-purple' => __('紫(纯色)', 'aksrt'),
'b-black' => __('黑(纯色)', 'aksrt'),
];
}
public static function sanitize_palette(string $value, string $fallback): string {
$value = sanitize_html_class($value);
return array_key_exists($value, self::palette_opts()) ? $value : $fallback;
}
}
-150
View File
@@ -1,150 +0,0 @@
<?php
/**
* Video player widget (zibll dplayer lite via data-aksrt-video + embed).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetVideoPlayer extends \WP_Widget {
public function __construct() {
parent::__construct(
'aksrt_video_player',
__('AKSRT 视频播放器', 'aksrt'),
[
'description' => __('侧栏/流体区嵌入视频(共享 aksrt-video 播放器)。', 'aksrt'),
'classname' => 'widget_aksrt_video_player',
]
);
}
public function widget($args, $instance): void {
$title = (string) ($instance['title'] ?? '');
$video_url = trim((string) ($instance['video_url'] ?? $instance['url'] ?? ''));
$poster = trim((string) ($instance['poster'] ?? $instance['pic'] ?? ''));
$autoplay = !empty($instance['autoplay']);
$loop = !empty($instance['loop']);
$muted = $autoplay || !empty($instance['muted']);
$hide_controller = !empty($instance['hide_controller']);
$scale = max(20, min(100, (int) ($instance['scale_height'] ?? 56)));
$aspect = (string) ($instance['aspect_ratio'] ?? '');
if ($video_url === '') {
if (current_user_can('manage_options')) {
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo function_exists('aksrt_get_widget_null')
? aksrt_get_widget_null(__('请配置视频 URL', 'aksrt')) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
: '<div class="c-red muted-box mb20"><b>' . esc_html__('视频播放器:', 'aksrt') . '</b>' . esc_html__('请配置视频 URL 或移出该模块', 'aksrt') . '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return;
}
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($aspect !== '' && str_contains($aspect, ':')) {
$parts = explode(':', $aspect);
if (count($parts) === 2 && (int) $parts[0] > 0) {
$scale = (int) round(((int) $parts[1] / (int) $parts[0]) * 100);
}
}
$embed = $this->get_embed_url($video_url);
$cls = 'aksrt-video-widget relative-h radius8' . ($hide_controller ? ' controller-hide' : '');
echo '<div class="' . esc_attr($cls) . '" style="padding-bottom:' . esc_attr((string) $scale) . '%">';
if ($embed !== '') {
$src = $embed;
if ($autoplay) {
$src = add_query_arg(['autoplay' => '1', 'mute' => '1'], $src);
}
echo '<iframe class="aksrt-video-widget__frame" src="' . esc_url($src) . '" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe>';
} else {
// Shared player stack (assets/js/video-player.js).
$type = 'mp4';
if (preg_match('/\.m3u8(\?|$)/i', $video_url)) {
$type = 'hls';
} elseif (preg_match('/\.flv(\?|$)/i', $video_url)) {
$type = 'flv';
}
printf(
'<div class="aksrt-video aksrt-video-widget__player" data-aksrt-video data-src="%s" data-type="%s"%s%s%s%s></div>',
esc_url($video_url),
esc_attr($type),
$poster !== '' ? ' data-poster="' . esc_url($poster) . '"' : '',
$autoplay ? ' data-autoplay="true"' : '',
$loop ? ' data-loop="true"' : '',
$muted ? ' data-muted="true"' : ''
);
}
echo '</div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function get_embed_url(string $url): string {
if (preg_match('#(?:youtube\.com/watch\?v=|youtu\.be/)([A-Za-z0-9_-]+)#', $url, $m)) {
return 'https://www.youtube.com/embed/' . rawurlencode($m[1]);
}
if (preg_match('#bilibili\.com/video/(BV[\w]+)#', $url, $m)) {
return 'https://player.bilibili.com/player.html?bvid=' . rawurlencode($m[1]) . '&high_quality=1';
}
if (preg_match('#v\.qq\.com/.+?/([A-Za-z0-9]+)\.html#', $url, $m)) {
return 'https://v.qq.com/txp/iframe/player.html?vid=' . rawurlencode($m[1]);
}
return '';
}
public function form($instance): void {
?>
<p>
<label><?php esc_html_e('标题', 'aksrt'); ?>
<input class="widefat" type="text" name="<?php echo esc_attr($this->get_field_name('title')); ?>" value="<?php echo esc_attr((string) ($instance['title'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('视频 URL(mp4/m3u8/flv 或 YouTube/B站/腾讯)', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('video_url')); ?>" value="<?php echo esc_attr((string) ($instance['video_url'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('封面图 URL', 'aksrt'); ?>
<input class="widefat" type="url" name="<?php echo esc_attr($this->get_field_name('poster')); ?>" value="<?php echo esc_attr((string) ($instance['poster'] ?? '')); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="20" max="100" name="<?php echo esc_attr($this->get_field_name('scale_height')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['scale_height'] ?? 56))); ?>">
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('autoplay')); ?>" value="1" <?php checked(!empty($instance['autoplay'])); ?>> <?php esc_html_e('自动播放', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('loop')); ?>" value="1" <?php checked(!empty($instance['loop'])); ?>> <?php esc_html_e('循环', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_controller')); ?>" value="1" <?php checked(!empty($instance['hide_controller'])); ?>> <?php esc_html_e('隐藏控制条样式', 'aksrt'); ?></label>
</p>
<?php
}
public function update($new_instance, $old_instance): array {
$business = [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'video_url' => esc_url_raw(trim((string) ($new_instance['video_url'] ?? ''))),
'poster' => esc_url_raw(trim((string) ($new_instance['poster'] ?? ''))),
'scale_height' => max(0, min(200, (int) ($new_instance['scale_height'] ?? 56))),
'aspect_ratio' => sanitize_text_field((string) ($new_instance['aspect_ratio'] ?? '')),
'autoplay' => !empty($new_instance['autoplay']) ? 1 : 0,
'loop' => !empty($new_instance['loop']) ? 1 : 0,
'muted' => !empty($new_instance['muted']) ? 1 : 0,
'hide_controller' => !empty($new_instance['hide_controller']) ? 1 : 0,
]; return $business;
}
}
-167
View File
@@ -1,167 +0,0 @@
<?php
/**
* Yiyan / Hitokoto widget (zibll widget_ui_yiyan shell + aksrt_render_yiyan_block).
*
* @package AKSRT
*/
namespace AKSRT\Widgets;
defined('ABSPATH') || exit;
class WidgetYiyan extends \WP_Widget {
/**
* @return array<string,string>
*/
private static function type_labels(): array {
return [
'' => __('随机', 'aksrt'),
'a' => __('动画', 'aksrt'),
'b' => __('漫画', 'aksrt'),
'c' => __('游戏', 'aksrt'),
'd' => __('文学', 'aksrt'),
'e' => __('原创', 'aksrt'),
'f' => __('来自网络', 'aksrt'),
'g' => __('其他', 'aksrt'),
'h' => __('影视', 'aksrt'),
'i' => __('诗词', 'aksrt'),
'j' => __('网易云', 'aksrt'),
'k' => __('哲学', 'aksrt'),
'l' => __('抖机灵', 'aksrt'),
];
}
private static function sanitize_type(string $type): string {
$type = strtolower(trim($type));
return preg_match('/^[a-l]$/', $type) ? $type : '';
}
public function __construct() {
parent::__construct(
'aksrt_yiyan',
__('AKSRT 一言轮播', 'aksrt'),
[
'description' => __('显示随机一言/每日语录,支持自动轮播。', 'aksrt'),
'classname' => 'widget_aksrt_yiyan yiyan-box',
]
);
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '';
$count = !empty($instance['count']) ? max(1, min(10, (int) $instance['count'])) : 5;
$interval = !empty($instance['interval']) ? max(3, min(30, (int) $instance['interval'])) : 5;
$type = self::sanitize_type((string) ($instance['type'] ?? ''));
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-quote-left';
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
if ($title !== '') {
echo $args['before_title'] . esc_html($title) . $args['after_title']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
// zibll shell: theme-box + yiyan-box main-bg text-center box-body radius8 main-shadow.
echo '<div class="theme-box yiyan-box-wrap">';
echo '<div class="yiyan-box main-bg text-center box-body radius8 main-shadow">';
if (function_exists('aksrt_render_yiyan_block')) {
aksrt_render_yiyan_block([
'class' => 'aksrt-yiyan yiyan',
'type' => $type,
'count' => $count,
'interval' => $interval,
'icon' => $icon,
]);
} else {
?>
<div class="aksrt-yiyan yiyan"
data-aksrt-yiyan
data-count="<?php echo esc_attr((string) $count); ?>"
data-interval="<?php echo esc_attr((string) $interval); ?>"
<?php echo $type !== '' ? ' data-type="' . esc_attr($type) . '"' : ''; ?>>
<div class="aksrt-yiyan__content">
<i class="<?php echo esc_attr($icon); ?> aksrt-yiyan__icon" aria-hidden="true"></i>
<p class="aksrt-yiyan__text"><?php esc_html_e('加载中…', 'aksrt'); ?></p>
<p class="aksrt-yiyan__from"></p>
</div>
<div class="aksrt-yiyan__dots" aria-hidden="true"></div>
</div>
<?php
}
echo '</div></div>';
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$title = !empty($instance['title']) ? (string) $instance['title'] : '一言';
$count = !empty($instance['count']) ? (int) $instance['count'] : 5;
$interval = !empty($instance['interval']) ? (int) $instance['interval'] : 5;
$type = self::sanitize_type((string) ($instance['type'] ?? ''));
$icon = !empty($instance['icon']) ? (string) $instance['icon'] : 'fas fa-quote-left';
$labels = self::type_labels();
$form_types = ['', 'a', 'd', 'i', 'k', 'h', 'j'];
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php esc_html_e('标题', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>"
name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text"
value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('count')); ?>"><?php esc_html_e('轮播条数', 'aksrt'); ?></label>
<input id="<?php echo esc_attr($this->get_field_id('count')); ?>"
name="<?php echo esc_attr($this->get_field_name('count')); ?>" type="number"
min="1" max="10" value="<?php echo esc_attr((string) $count); ?>" style="width:80px;">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('interval')); ?>"><?php esc_html_e('切换间隔(秒)', 'aksrt'); ?></label>
<input id="<?php echo esc_attr($this->get_field_id('interval')); ?>"
name="<?php echo esc_attr($this->get_field_name('interval')); ?>" type="number"
min="3" max="30" value="<?php echo esc_attr((string) $interval); ?>" style="width:80px;">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('type')); ?>"><?php esc_html_e('分类', 'aksrt'); ?></label>
<select id="<?php echo esc_attr($this->get_field_id('type')); ?>"
name="<?php echo esc_attr($this->get_field_name('type')); ?>">
<?php foreach ($form_types as $code) : ?>
<option value="<?php echo esc_attr($code); ?>" <?php selected($type, $code); ?>>
<?php echo esc_html($labels[$code] ?? $code); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('icon')); ?>"><?php esc_html_e('图标', 'aksrt'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('icon')); ?>"
name="<?php echo esc_attr($this->get_field_name('icon')); ?>" type="text"
value="<?php echo esc_attr($icon); ?>">
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'count' => max(1, min(10, absint($new_instance['count'] ?? 5))),
'interval' => max(3, min(30, absint($new_instance['interval'] ?? 5))),
'type' => self::sanitize_type((string) ($new_instance['type'] ?? '')),
'icon' => sanitize_text_field((string) ($new_instance['icon'] ?? 'fas fa-quote-left')),
];
}
}