feat(topics): restore topic taxonomy workflow

This commit is contained in:
kate522 committed 2026-11-10 22:31:11 +08:00
1 parent f3dd1ec3b1
commit a254cbd3e9
59 files changed
+771 -192

No files matched your search

+3 -6
View File
@@ -3,11 +3,8 @@
* Admin post list enhancements: interaction + sales columns and the
* quick/bulk edit fields behind them.
*
* Mirrors the reference theme's post list: a sortable “销售数据” column, a
* product-type filter, and four quick-edit field groups (interaction data,
* article config, pay parameters, custom filters). The reference theme hooks
* those boxes onto its topics taxonomy column; this project has no topics
* taxonomy, so they hang off the interaction-data column instead.
* Adds a sortable sales column, a product-type filter, and quick-edit fields
* for interaction data, article config, pay parameters, and custom filters.
*
* @package Jio
*/
@@ -280,7 +277,7 @@ class PostListEnhance {
* @param string $post_type
*/
public function render_edit_boxes($column_name, $post_type): void {
if ($post_type !== 'post' || (string) $column_name !== self::COLUMN_STATS) {
if ($post_type !== 'post' || (string) $column_name !== 'taxonomy-topics') {
return;
}
+5 -4
View File
@@ -21,7 +21,7 @@ class PageDisplayController extends ControllerBase {
}
public function register_routes(): void {
register_rest_route(self::API_NAMESPACE, '/settings/schema/page_display/search/(?P<source>posts|categories|tags)', [
register_rest_route(self::API_NAMESPACE, '/settings/schema/page_display/search/(?P<source>posts|categories|tags|topics|terms)', [
'methods' => 'GET',
'callback' => [$this, 'search_options'],
'permission_callback' => [$this, 'check_permission'],
@@ -52,7 +52,7 @@ class PageDisplayController extends ControllerBase {
return $this->success(['data' => $items]);
}
$taxonomy = $source === 'categories' ? 'category' : 'post_tag';
$taxonomy = ['categories' => 'category', 'tags' => 'post_tag', 'topics' => 'topics', 'terms' => ['category', 'topics']][$source] ?? 'category';
$terms = get_terms([
'taxonomy' => $taxonomy,
'hide_empty' => false,
@@ -64,8 +64,9 @@ class PageDisplayController extends ControllerBase {
if (is_wp_error($terms)) {
return $this->error($terms->get_error_message(), 400);
}
$items = array_map(static function ($term): array {
return ['label' => $term->name, 'value' => (int) $term->term_id];
$items = array_map(static function ($term) use ($source): array {
$label = $source === 'terms' ? ($term->taxonomy === 'topics' ? __('专题:', 'jio') : __('分类:', 'jio')) . $term->name : $term->name;
return ['label' => $label, 'value' => (int) $term->term_id];
}, $terms);
return $this->success(['data' => $items]);
}
+2 -1
View File
@@ -297,12 +297,13 @@ class GlobalSchema {
['key' => 'search_custom_key_s', 'label' => '自定义匹配范围', 'type' => 'switch', 'default' => true, 'help' => '启用后可配置搜索匹配字段范围(标题/内容/分类/标签等)'],
['key' => 'search_custom_key', 'label' => '匹配字段', 'type' => 'checkbox', 'default' => ['title', 'content', 'excerpt', 'category', 'post_tag'], 'options' => [
['label' => '标题', 'value' => 'title'], ['label' => '内容', 'value' => 'content'], ['label' => '简介', 'value' => 'excerpt'],
['label' => '分类', 'value' => 'category'], ['label' => '标签', 'value' => 'post_tag'],
['label' => '分类', 'value' => 'category'], ['label' => '标签', 'value' => 'post_tag'], ['label' => '专题', 'value' => 'topics'],
['label' => '评论', 'value' => 'coment'], ['label' => '作者昵称', 'value' => 'user'],
]],
['key' => 'search_facets', 'label' => '搜索筛选', 'type' => 'fieldset', 'sanitize_type' => 'array', 'default' => [], 'dependsOn' => ['key' => 'search_ms_s', 'value' => false], 'help' => '搜索结果中显示的筛选列表;启用 Meilisearch 后由搜索引擎生成关联筛选。', 'fields' => [
['key' => 'category', 'label' => '[文章] 分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => []],
['key' => 'post_tag', 'label' => '[文章] 标签', 'type' => 'taxonomy_select', 'taxonomy' => 'post_tag', 'multiple' => true, 'default' => []],
['key' => 'topics', 'label' => '[文章] 专题', 'type' => 'taxonomy_select', 'taxonomy' => 'topics', 'multiple' => true, 'default' => []],
]],
]],
['key' => 'category-search', 'title' => '分类搜索', 'fields' => [
@@ -33,6 +33,7 @@ class SchemaPostListList {
['label' => '首页', 'value' => 'home'],
['label' => '分类页', 'value' => 'cat'],
['label' => '标签页', 'value' => 'tag'],
['label' => '专题页', 'value' => 'topics'],
]],
],
],
@@ -49,10 +50,11 @@ class SchemaPostListList {
['label' => '点赞数', 'value' => 'like'],
['label' => '评论', 'value' => 'comm'],
]],
['key' => 'list_badge_show', 'label' => '显示列表标签', 'type' => 'checkbox', 'default' => ['pay', 'cat', 'tag'], 'options' => [
['key' => 'list_badge_show', 'label' => '显示列表标签', 'type' => 'checkbox', 'default' => ['pay', 'tag', 'topics', 'cat'], 'options' => [
['label' => '付费信息', 'value' => 'pay'],
['label' => '分类', 'value' => 'cat'],
['label' => '标签', 'value' => 'tag'],
['label' => '专题', 'value' => 'topics'],
]],
],
],
@@ -72,8 +74,9 @@ class SchemaPostListList {
], 'help' => '文字、自动图文和多图模式仅在开启侧栏的页面有效。'],
['key' => 'list_card_home', 'label' => '首页列表卡片模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
['key' => 'list_card_tag', 'label' => '标签页列表卡片模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
['key' => 'list_card_topics', 'label' => '专题页列表卡片模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
['key' => 'list_card_author', 'label' => '用户页列表卡片模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
['key' => 'list_card_cat', 'label' => '分类页自定义卡片模式', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
['key' => 'list_card_cat', 'label' => '分类、专题页自定义卡片模式', 'type' => 'taxonomy_select', 'taxonomy' => 'category,topics', 'multiple' => true, 'default' => [], 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
[
'key' => 'list_list_option',
'label' => '列表模式 UI 配置',
+25 -4
View File
@@ -22,6 +22,7 @@ class SchemaPageDisplay {
'homepage' => self::homepage(),
'category' => self::category(),
'tag' => self::tag(),
'topics' => self::topics(),
'user-home' => self::user_home(),
'other' => self::other(),
],
@@ -60,7 +61,7 @@ class SchemaPageDisplay {
private static function header_components(): array {
return self::section('header-components', '顶部多功能组件', [
self::notice('顶部多功能组件显示在导航栏下方,支持视频、图片、幻灯片、搜索和图标卡片;各组件可按电脑端、移动端单独启用。'),
self::field('header_slider_show', '启用顶部多功能组件', 'checkbox', [], ['description' => '选择允许显示组件的页面;未选择任何页面时,前端不会显示。', 'options' => self::options(['home' => '首页', 'category' => '分类页', 'tag' => '标签页', 'author' => '公开用户主页', 'user_center' => '用户中心', 'single' => '文章页', 'search' => '搜索页', 'page' => '其它页面'])]),
self::field('header_slider_show', '启用顶部多功能组件', 'checkbox', [], ['description' => '选择允许显示组件的页面;未选择任何页面时,前端不会显示。', 'options' => self::options(['home' => '首页', 'category' => '分类页', 'tag' => '标签页', 'topics' => '专题页', 'author' => '公开用户主页', 'user_center' => '用户中心', 'single' => '文章页', 'search' => '搜索页', 'page' => '其它页面'])]),
self::field('header_slider_show_type', '顶部多功能组件的显示规则', 'radio', '', ['dependsOn' => self::not_empty('header_slider_show'), 'inline' => true, 'options' => self::options(['' => '全部显示', 'only_pc' => '仅在PC端显示', 'only_sm' => '仅在移动端显示'])]),
self::field('header_slider_nav_color', '导航栏文字颜色', 'color', '#ffffff', ['dependsOn' => self::not_empty('header_slider_show')]),
self::field('header_slider', '背景内容', 'group', self::header_slider_defaults(), ['dependsOn' => self::not_empty('header_slider_show'), 'min' => 1, 'button_title' => '添加背景项目', 'fields' => self::slider_item_fields()]),
@@ -112,7 +113,7 @@ class SchemaPageDisplay {
self::field('index_list_title', '显示标题', 'textarea', '', ['dependsOn' => self::enabled('home_posts_list_s'), 'sanitize_type' => 'html', 'rows' => 1]),
self::field('home_exclude_posts', '排除文章', 'async_select', '', ['dependsOn' => self::enabled('home_posts_list_s'), 'source' => 'posts', 'multiple' => true, 'sortable' => true]),
self::field('home_exclude_cats', '排除分类', 'async_select', '', ['dependsOn' => self::enabled('home_posts_list_s'), 'source' => 'categories', 'multiple' => true, 'sortable' => true]),
self::field('home_lists', '更多栏目', 'group', [], ['dependsOn' => self::enabled('home_posts_list_s'), 'button_title' => '添加栏目', 'fields' => [self::field('title', '自定义标题', 'textarea', '', ['sanitize_type' => 'html', 'rows' => 1]), self::field('term_id', '显示内容', 'async_select', '', ['source' => 'categories']), self::field('orderby', '排序方式设置', 'select', '', ['options' => self::post_order_options(true)])]]),
self::field('home_lists', '更多栏目', 'group', [], ['dependsOn' => self::enabled('home_posts_list_s'), 'button_title' => '添加栏目', 'fields' => [self::field('title', '自定义标题', 'textarea', '', ['sanitize_type' => 'html', 'rows' => 1]), self::field('term_id', '显示内容', 'async_select', '', ['source' => 'terms']), self::field('orderby', '排序方式设置', 'select', '', ['options' => self::post_order_options(true)])]]),
]);
}
@@ -123,11 +124,13 @@ class SchemaPageDisplay {
self::field('cat_default_cover', '封面图', 'upload', self::asset('assets/img/user_t.jpg'), ['dependsOn' => self::enabled('page_cover_cat_s'), 'help' => '默认封面图,建议尺寸2000x800。']),
self::field('cat_orderby_s', '排序方式按钮', 'switcher', true),
self::field('cat_orderby_option', '排序方式设置', 'fieldset', [], ['dependsOn' => self::enabled('cat_orderby_s'), 'fields' => self::orderby_fields()]),
self::notice('AJAX筛选菜单列表:显示分类、标签菜单,通过 AJAX 获取内容。'),
self::notice('AJAX筛选菜单列表:显示分类、标签、专题菜单,通过 AJAX 获取内容。'),
self::field('ajax_list_cat_cat', '分类筛选', 'switcher', true),
self::field('ajax_list_option_cat_cat', '分类筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_cat_cat'), 'fields' => self::ajax_fields('categories')]),
self::field('ajax_list_cat_tag', '标签筛选', 'switcher', false),
self::field('ajax_list_option_cat_tag', '标签筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_cat_tag'), 'fields' => self::ajax_fields('tags')]),
self::field('ajax_list_cat_topics', '专题筛选', 'switcher', false),
self::field('ajax_list_option_cat_topics', '专题筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_cat_topics'), 'fields' => self::ajax_fields('topics')]),
]);
}
@@ -138,11 +141,29 @@ class SchemaPageDisplay {
self::field('tag_default_cover', '封面图', 'upload', self::asset('assets/img/user_t.jpg'), ['dependsOn' => self::enabled('page_cover_tag_s'), 'help' => '默认封面图,建议尺寸2000x800。']),
self::field('tag_orderby_s', '排序方式按钮', 'switcher', true),
self::field('tag_orderby_option', '排序方式设置', 'fieldset', [], ['dependsOn' => self::enabled('tag_orderby_s'), 'fields' => self::orderby_fields()]),
self::notice('AJAX筛选菜单列表:显示分类、标签菜单,通过 AJAX 获取内容。'),
self::notice('AJAX筛选菜单列表:显示分类、标签、专题菜单,通过 AJAX 获取内容。'),
self::field('ajax_list_tag_cat', '分类筛选', 'switcher', false),
self::field('ajax_list_option_tag_cat', '分类筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_tag_cat'), 'fields' => self::ajax_fields('categories')]),
self::field('ajax_list_tag_tag', '标签筛选', 'switcher', true),
self::field('ajax_list_option_tag_tag', '标签筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_tag_tag'), 'fields' => self::ajax_fields('tags')]),
self::field('ajax_list_tag_topics', '专题筛选', 'switcher', false),
self::field('ajax_list_option_tag_topics', '专题筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_tag_topics'), 'fields' => self::ajax_fields('topics')]),
]);
}
private static function topics(): array {
return self::section('topics', '专题页面', [
self::field('topics_post_count_s', '显示文章数量', 'switcher', true),
self::field('topics_default_cover', '封面图', 'upload', self::asset('assets/img/user_t.jpg'), ['help' => '默认封面图,建议尺寸2000x800。']),
self::field('topics_orderby_s', '排序方式按钮', 'switcher', true),
self::field('topics_orderby_option', '排序方式设置', 'fieldset', [], ['dependsOn' => self::enabled('topics_orderby_s'), 'fields' => self::orderby_fields()]),
self::notice('AJAX筛选菜单列表:显示分类、标签、专题菜单,通过 AJAX 获取内容。'),
self::field('ajax_list_topics_cat', '分类筛选', 'switcher', false),
self::field('ajax_list_option_topics_cat', '分类筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_topics_cat'), 'fields' => self::ajax_fields('categories')]),
self::field('ajax_list_topics_tag', '标签筛选', 'switcher', false),
self::field('ajax_list_option_topics_tag', '标签筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_topics_tag'), 'fields' => self::ajax_fields('tags')]),
self::field('ajax_list_topics_topics', '专题筛选', 'switcher', true),
self::field('ajax_list_option_topics_topics', '专题筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_topics_topics'), 'fields' => self::ajax_fields('topics')]),
]);
}
+8
View File
@@ -136,6 +136,7 @@ class SchemaPostList {
['key' => 'custom_filter_show', 'label' => '高级筛选默认显示', 'type' => 'checkbox', 'default' => [], 'options' => [
['label' => '分类页面显示', 'value' => 'cat'],
['label' => '标签页面显示', 'value' => 'tag'],
['label' => '专题页面显示', 'value' => 'topics'],
], 'description' => '勾选后将会在对应页面默认显示全部筛选,您可以在下方为单独的分类添加自定义筛选'],
['key' => 'custom_filter', 'label' => '自定义高级筛选明细', 'type' => 'repeater', 'default' => [], 'addText' => '添加筛选类别', 'description' => '添加筛选类型及明细', 'fields' => [
['key' => 'name', 'label' => '类型名称', 'type' => 'input', 'default' => ''],
@@ -157,6 +158,11 @@ class SchemaPostList {
['key' => 'lists', 'label' => '按钮列表', 'type' => 'async_select', 'source' => 'tags', 'multiple' => true, 'sortable' => true, 'default' => [], 'description' => '选择并排序需要显示的按钮'],
['key' => 'dropdown', 'label' => '下拉列表', 'type' => 'switch', 'default' => false, 'description' => '用下拉框显示更多内容'],
]],
['key' => 'topics_s', 'label' => '专题筛选', 'type' => 'switch', 'default' => false, 'description' => '在所选分类页显示的专题筛选列表'],
['key' => 'topics_lists', 'label' => ' ', 'type' => 'fieldset', 'default' => ['lists' => [], 'dropdown' => false], 'dependsOn' => ['key' => 'topics_s', 'value' => true], 'fields' => [
['key' => 'lists', 'label' => '按钮列表', 'type' => 'async_select', 'source' => 'topics', 'multiple' => true, 'sortable' => true, 'default' => []],
['key' => 'dropdown', 'label' => '下拉列表', 'type' => 'switch', 'default' => false],
]],
['key' => 'orderby_s', 'label' => '排序方式', 'type' => 'switch', 'default' => true, 'description' => '在所选分类页显示排序方式按钮'],
['key' => 'orderby_lists', 'label' => ' ', 'type' => 'fieldset', 'default' => ['lists' => ['modified', 'views', 'like', 'comment_count'], 'dropdown' => false], 'dependsOn' => ['key' => 'orderby_s', 'value' => true], 'fields' => [
['key' => 'lists', 'label' => '显示排序方式', 'type' => 'select', 'multiple' => true, 'sortable' => true, 'default' => ['modified', 'views', 'like', 'comment_count'], 'options' => [
@@ -264,6 +270,8 @@ class SchemaPostList {
['key' => 'post_article_s', 'label' => '启用前台投稿', 'type' => 'switcher', 'default' => true, 'description' => '此功能启用后,可以在权限管理中设置用户的发布、审核权限'],
['key' => 'post_article_btn_txte', 'label' => '按钮文字', 'type' => 'input', 'default' => '发布文章', 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '投稿按钮的显示的文字,推荐4个字更加协调'],
['key' => 'post_article_cat', 'label' => '投稿允许选择的分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'placeholder' => '输入关键词以搜索分类', 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '投稿允许选择的分类,为空则允许选择全部分类,所选分类下如果有子分类则会自动显示,如果选择了上级分类,则不要再选该分类下的子分类。注意:请确保所选分类包含子分类总数不要超过50个,否则可能会引起页面崩溃'],
['key' => 'post_article_topics_s', 'label' => '允许选择专题', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'post_article_s', 'value' => true]],
['key' => 'post_article_topics', 'label' => '投稿允许选择的专题', 'type' => 'taxonomy_select', 'taxonomy' => 'topics', 'multiple' => true, 'default' => [], 'dependsOn' => ['all' => [['key' => 'post_article_s', 'value' => true], ['key' => 'post_article_topics_s', 'value' => true]]]],
['key' => 'post_article_custom_filter_s', 'label' => '允许选择高级筛选', 'type' => 'switcher', 'default' => true, 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '此功能启用后,请先在文章列表/高级筛选中添加高级筛选明细'],
['key' => 'post_article_tag', 'label' => '允许插入标签', 'type' => 'switcher', 'default' => true, 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '前台投稿时候允许设置文章标签'],
['key' => 'post_article_user', 'label' => '免登陆投稿发布用户', 'type' => 'user_select', 'default' => 1, 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '当选择投稿权限为无需登录就能投稿时,投稿文章的用户'],
+22
View File
@@ -143,6 +143,7 @@ final class CreatorRest {
'permalink' => $source instanceof \WP_Post ? get_permalink($source) : '',
'isPublished'=> $source instanceof \WP_Post && $source->post_status === 'publish',
'categories' => $terms['categories'],
'topics' => $terms['topics'],
'tags' => $terms['tags'],
'custom' => $terms['custom'],
'customDefs' => $terms['customDefs'],
@@ -222,6 +223,7 @@ final class CreatorRest {
'title' => (string) $request->get_param('title'),
'content' => (string) $request->get_param('content'),
'categories' => (array) $request->get_param('categories'),
'topics' => (array) $request->get_param('topics'),
'tags' => (array) $request->get_param('tags'),
'custom' => (array) $request->get_param('custom'),
'featured' => (array) $request->get_param('featured'),
@@ -250,6 +252,7 @@ final class CreatorRest {
'post_title' => $payload['title'],
'post_content' => $payload['content'],
'category' => $payload['categories'],
'topics' => $payload['topics'],
'tags' => implode(',', array_map('strval', $payload['tags'])),
'featured_data'=> $payload['featured'],
'schedule' => $payload['schedule'],
@@ -295,6 +298,7 @@ final class CreatorRest {
'title' => (string) $request->get_param('title'),
'content' => (string) $request->get_param('content'),
'categories' => (array) $request->get_param('categories'),
'topics' => (array) $request->get_param('topics'),
'tags' => (array) $request->get_param('tags'),
'custom' => (array) $request->get_param('custom'),
'featured' => (array) $request->get_param('featured'),
@@ -334,6 +338,7 @@ final class CreatorRest {
'post_title' => $payload['title'],
'post_content' => $payload['content'],
'category' => $payload['categories'],
'topics' => $payload['topics'],
'tags' => implode(',', array_map('strval', $payload['tags'])),
'featured_data' => $payload['featured'],
'schedule' => $payload['schedule'],
@@ -519,6 +524,7 @@ final class CreatorRest {
return [
'categories' => array_values(array_filter(array_map('absint', (array) $payload['categories']))),
'topics' => array_values(array_filter(array_map('absint', (array) $payload['topics']))),
'tags' => array_values(array_filter(array_map('sanitize_text_field', (array) $payload['tags']))),
'custom' => is_array($payload['custom']) ? $payload['custom'] : [],
'featured' => is_array($payload['featured']) ? $payload['featured'] : [],
@@ -567,6 +573,21 @@ final class CreatorRest {
$current = $source instanceof \WP_Post && $snapshot === []
? array_map('intval', wp_get_post_categories($source->ID))
: array_map('intval', (array) ($snapshot['categories'] ?? []));
$topics = [];
if (!empty($settings['allow_topics'])) {
foreach (SubmitTopics::tree($settings) as $node) {
$topics[] = [
'id' => (int) $node['term']->term_id,
'name' => (string) $node['term']->name,
'depth' => (int) $node['depth'],
'path' => (string) $node['path'],
];
}
}
$selected_topics = $source instanceof \WP_Post && $snapshot === []
? wp_get_post_terms($source->ID, 'topics', ['fields' => 'ids'])
: ($snapshot['topics'] ?? []);
$selected_topics = is_array($selected_topics) ? SubmitTopics::filter_ids($selected_topics, $settings) : [];
$tags = $source instanceof \WP_Post && $snapshot === []
? wp_get_post_tags($source->ID, ['fields' => 'names'])
: array_map('strval', (array) ($snapshot['tags'] ?? []));
@@ -610,6 +631,7 @@ final class CreatorRest {
return [
'categories' => ['items' => $categories, 'selected' => $current],
'topics' => ['enabled' => !empty($settings['allow_topics']), 'items' => $topics, 'selected' => $selected_topics],
'tags' => [
'items' => is_array($tags) ? array_values($tags) : [],
'enabled' => $tag_enabled,
-1
View File
@@ -148,7 +148,6 @@ class DomainModel {
'description' => '',
'show_sidebar' => '',
'show_header' => true,
'show_featured' => false,
];
}
+5
View File
@@ -231,6 +231,11 @@ trait FrontSubmitSave {
if (isset($data['category'])) {
wp_set_post_categories((int) $in_id, $categories);
}
if (!empty($settings['allow_topics'])) {
$raw_topics = $data['topics'] ?? [];
$topic_ids = SubmitTopics::filter_ids(is_array($raw_topics) ? $raw_topics : [$raw_topics], $settings);
wp_set_post_terms((int) $in_id, $topic_ids, 'topics');
}
if (!empty($settings['allow_tags']) && array_key_exists('tags', $data) && $tags_raw === '') {
wp_set_post_tags((int) $in_id, []);
}
+26
View File
@@ -29,6 +29,7 @@ class Setup {
add_filter('pre_option_link_manager_enabled', '__return_true');
add_action('init', [$this, 'register_nav_menus'], 0);
add_action('init', [$this, 'register_post_types'], 1);
add_action('init', [$this, 'register_taxonomies'], 1);
add_action('init', [$this, 'register_meta_fields'], 5);
// 文章浏览量统计(单次访问)
add_action('wp', 'jio_set_post_views');
@@ -124,6 +125,31 @@ class Setup {
]);
}
public function register_taxonomies(): void {
register_taxonomy('topics', ['post'], [
'description' => __('添加文章专题', 'jio'),
'hierarchical' => true,
'labels' => [
'name' => __('专题', 'jio'),
'singular_name' => __('专题', 'jio'),
'search_items' => __('搜索专题', 'jio'),
'all_items' => __('所有专题', 'jio'),
'parent_item' => __('父专题', 'jio'),
'parent_item_colon' => __('父专题:', 'jio'),
'edit_item' => __('编辑专题', 'jio'),
'update_item' => __('更新专题', 'jio'),
'add_new_item' => __('添加新专题', 'jio'),
'new_item_name' => __('新专题名称', 'jio'),
'menu_name' => __('专题', 'jio'),
],
'show_ui' => true,
'show_in_menu' => true,
'show_in_rest' => true,
'show_admin_column' => true,
'query_var' => true,
]);
}
/**
* 注册资源 post meta + 分类 term meta(jio_flow)
*
+7
View File
@@ -441,6 +441,10 @@ final class SubmissionVersion {
}
wp_set_post_categories($post_id, $cats);
}
if (!empty($settings['allow_topics']) && array_key_exists('topics', $snapshot)) {
$topics = is_array($snapshot['topics']) ? $snapshot['topics'] : [];
wp_set_post_terms($post_id, SubmitTopics::filter_ids($topics, $settings), 'topics');
}
if (array_key_exists('tags', $snapshot)) {
$tags = is_array($snapshot['tags']) ? $snapshot['tags'] : [];
wp_set_post_tags($post_id, array_values(array_filter(array_map('sanitize_text_field', $tags))));
@@ -480,6 +484,9 @@ final class SubmissionVersion {
if (isset($snapshot['categories']) && is_array($snapshot['categories'])) {
$clean['categories'] = array_values(array_unique(array_filter(array_map('absint', $snapshot['categories']))));
}
if (isset($snapshot['topics']) && is_array($snapshot['topics'])) {
$clean['topics'] = array_values(array_unique(array_filter(array_map('absint', $snapshot['topics']))));
}
if (isset($snapshot['tags'])) {
$tags = is_array($snapshot['tags']) ? $snapshot['tags'] : preg_split('/[,,\n]+/u', (string) $snapshot['tags']);
$clean['tags'] = array_values(array_filter(array_map('sanitize_text_field', (array) $tags)));
+88
View File
@@ -0,0 +1,88 @@
<?php
/**
* Topics available to front-end authors.
*
* @package Jio
*/
namespace Jio\Core;
defined('ABSPATH') || exit;
final class SubmitTopics {
/** @return array<int, array{term:\WP_Term, depth:int, path:string}> */
public static function tree(array $settings): array {
if (empty($settings['allow_topics'])) {
return [];
}
$terms = get_terms([
'taxonomy' => 'topics',
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
'number' => 0,
]);
if (!is_array($terms)) {
return [];
}
$allowed = self::allowed_ids($settings);
if ($allowed !== null) {
$terms = array_values(array_filter($terms, static fn($term): bool => isset($allowed[(int) $term->term_id])));
}
$present = [];
foreach ($terms as $term) {
$present[(int) $term->term_id] = true;
}
$children = [];
foreach ($terms as $term) {
$parent = (int) $term->parent;
$children[isset($present[$parent]) ? $parent : 0][] = $term;
}
$tree = [];
$walk = static function (int $parent, int $depth, string $prefix) use (&$walk, &$tree, $children): void {
foreach ($children[$parent] ?? [] as $term) {
$path = $prefix === '' ? (string) $term->name : $prefix . ' / ' . $term->name;
$tree[] = ['term' => $term, 'depth' => $depth, 'path' => $path];
$walk((int) $term->term_id, $depth + 1, $path);
}
};
$walk(0, 0, '');
return $tree;
}
/** @return int[] */
public static function filter_ids(array $ids, array $settings): array {
if (empty($settings['allow_topics'])) {
return [];
}
$allowed = self::allowed_ids($settings);
$ids = array_values(array_unique(array_filter(array_map('absint', $ids))));
$valid = [];
foreach ($ids as $id) {
if (($allowed === null || isset($allowed[$id])) && term_exists($id, 'topics')) {
$valid[] = $id;
}
}
return $valid;
}
/** @return array<int,true>|null */
private static function allowed_ids(array $settings): ?array {
$roots = array_values(array_unique(array_filter(array_map('absint', (array) ($settings['allowed_topic_ids'] ?? [])))));
if ($roots === []) {
return null;
}
$allowed = [];
foreach ($roots as $id) {
$allowed[$id] = true;
$descendants = get_term_children($id, 'topics');
foreach (is_array($descendants) ? $descendants : [] as $child) {
$allowed[(int) $child] = true;
}
}
return $allowed;
}
}
-7
View File
@@ -91,13 +91,6 @@ class Templates {
return $templates;
}
/**
* Load dedicated taxonomy templates.
*/
public function load_taxonomy_template(string $template): string {
return $template;
}
public function render_archive_shortcode(array $attrs = [], string $content = ''): string {
$template = JIO_DIR . '/templates/shortcodes/archive.php';
if (!file_exists($template)) {
+102 -22
View File
@@ -12,7 +12,7 @@ class TermMeta {
private static $instance = null;
/** Supported taxonomies */
private $taxonomies = ['category', 'post_tag'];
private $taxonomies = ['category', 'post_tag', 'topics'];
/** Meta key for storing term-level settings */
const META_KEY = DomainModel::TERM_META_KEY;
@@ -31,9 +31,61 @@ class TermMeta {
add_action("edited_{$tax}", [$this, 'save_term_meta']);
}
add_action('admin_enqueue_scripts', [$this, 'enqueue_topic_media']);
add_filter('manage_edit-topics_columns', [$this, 'topic_columns']);
add_filter('manage_topics_custom_column', [$this, 'render_topic_column'], 10, 3);
add_filter('topics_row_actions', [$this, 'topic_row_actions'], 10, 2);
add_action('pre_get_posts', [$this, 'filter_archive_query']);
}
public function topic_columns(array $columns): array {
return ['jio_cover' => __('图像', 'jio')] + $columns;
}
public function render_topic_column(string $content, string $column, int $term_id): string {
if ($column !== 'jio_cover') {
return $content;
}
$url = esc_url_raw((string) self::get_meta_value($term_id, 'cover_url'));
return $url === ''
? '&mdash;'
: '<img src="' . esc_url($url) . '" alt="" style="width:55px;height:28px;object-fit:cover">';
}
public function topic_row_actions(array $actions, \WP_Term $term): array {
$actions['jio_term_id'] = sprintf(esc_html__('ID:%d', 'jio'), (int) $term->term_id);
return $actions;
}
public function enqueue_topic_media(string $hook): void {
if (!in_array($hook, ['edit-tags.php', 'term.php'], true)) {
return;
}
$screen = get_current_screen();
if (!$screen || $screen->taxonomy !== 'topics') {
return;
}
wp_enqueue_media();
wp_add_inline_script('media-views', <<<'JS'
document.addEventListener('click', function (event) {
const button = event.target.closest('[data-jio-topic-cover-select]');
if (!button || typeof wp === 'undefined' || !wp.media) return;
event.preventDefault();
const field = button.closest('.form-field');
const input = field && field.querySelector('[name="jio_term_meta[cover_url]"]');
if (!input) return;
const frame = wp.media({ title: button.textContent.trim(), library: { type: 'image' }, multiple: false });
frame.on('select', function () {
const image = frame.state().get('selection').first();
if (image) input.value = image.get('url') || '';
});
frame.open();
});
JS
);
}
/**
* Get term meta with defaults.
*/
@@ -59,17 +111,25 @@ class TermMeta {
* @return array<string,mixed>
*/
public static function resolve_archive_settings(\WP_Term $term): array {
$taxonomy = $term->taxonomy === 'post_tag' ? 'post_tag' : 'category';
$global = $taxonomy === 'post_tag' && class_exists('\Jio\Settings\Settings')
? \Jio\Settings\Settings::get_tag_page_settings()
: (class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_category_page_settings() : [
$taxonomy = in_array($term->taxonomy, ['category', 'post_tag', 'topics'], true) ? $term->taxonomy : 'category';
if (class_exists('\Jio\Settings\Settings')) {
if ($taxonomy === 'topics') {
$global = \Jio\Settings\Settings::get_topics_page_settings();
} elseif ($taxonomy === 'post_tag') {
$global = \Jio\Settings\Settings::get_tag_page_settings();
} else {
$global = \Jio\Settings\Settings::get_category_page_settings();
}
} else {
$global = [
'header_style' => 'cover',
'cover_enabled' => true,
'desc_enabled' => true,
'filter_bar' => true,
'sidebar' => true,
'sidebar' => $taxonomy !== 'topics',
'per_page' => 12,
]);
];
}
$saved = get_term_meta($term->term_id, self::META_KEY, true);
if (!is_array($saved)) {
@@ -96,7 +156,7 @@ class TermMeta {
$cover_url = isset($saved['cover_url']) ? esc_url_raw((string) $saved['cover_url']) : '';
if ($cover_url === '' && !empty($global['cover_enabled'])) {
$cover_id = (int) get_term_meta($term->term_id, 'jio_category_cover_id', true);
$cover_id = $taxonomy === 'category' ? (int) get_term_meta($term->term_id, 'jio_category_cover_id', true) : 0;
$cover_url = $cover_id > 0 ? (string) wp_get_attachment_url($cover_id) : '';
if ($cover_url === '') {
$cover_url = esc_url_raw((string) ($global['default_cover'] ?? ''));
@@ -108,9 +168,7 @@ class TermMeta {
$description = (string) $term->description;
}
// 分类高级筛选语义:cat 页按 cat_custom_filter 全量覆盖
// 筛选项目,未匹配或 tag 页回退默认配置,custom_filter_show 决定自定义筛选显示。
$page = $taxonomy === 'post_tag' ? 'tag' : 'cat';
$page = $taxonomy === 'post_tag' ? 'tag' : ($taxonomy === 'topics' ? 'topics' : 'cat');
$custom_filter = class_exists('\Jio\Settings\Settings')
? \Jio\Settings\Settings::get_archive_custom_filter_config((int) $term->term_id, $page)
: ['custom_enabled' => false, 'custom_filters' => 'all', 'overrides' => []];
@@ -120,6 +178,8 @@ class TermMeta {
$ajax_category = is_array($global['ajax_category'] ?? null) ? $global['ajax_category'] : [];
$ajax_tag_enabled = !empty($global['ajax_tag_enabled']);
$ajax_tag = is_array($global['ajax_tag'] ?? null) ? $global['ajax_tag'] : [];
$ajax_topics_enabled = !empty($global['ajax_topics_enabled']);
$ajax_topics = is_array($global['ajax_topics'] ?? null) ? $global['ajax_topics'] : [];
$orderby_enabled = !empty($global['orderby_enabled']);
$orderby_option = is_array($global['orderby_option'] ?? null) ? $global['orderby_option'] : [];
if (array_key_exists('cat_s', $overrides)) {
@@ -130,6 +190,10 @@ class TermMeta {
$ajax_tag_enabled = !empty($overrides['tag_s']);
$ajax_tag = is_array($overrides['tag_lists'] ?? null) ? $overrides['tag_lists'] : $ajax_tag;
}
if (array_key_exists('topics_s', $overrides)) {
$ajax_topics_enabled = !empty($overrides['topics_s']);
$ajax_topics = is_array($overrides['topics_lists'] ?? null) ? $overrides['topics_lists'] : $ajax_topics;
}
if (array_key_exists('orderby_s', $overrides)) {
$orderby_enabled = !empty($overrides['orderby_s']);
$orderby_option = is_array($overrides['orderby_lists'] ?? null) ? $overrides['orderby_lists'] : $orderby_option;
@@ -143,16 +207,18 @@ class TermMeta {
'header_style' => $header_style,
'show_sidebar' => $sidebar_enabled,
'show_cover' => $header_style === 'cover' && !empty($global['cover_enabled']),
'show_desc' => $taxonomy === 'post_tag' ? $description !== '' : (!empty($global['desc_enabled']) && $description !== ''),
'filter_bar' => !empty($global['filter_bar']) || $ajax_category_enabled || $ajax_tag_enabled || $orderby_enabled || !empty($custom_filter['custom_enabled']),
'show_desc' => $taxonomy !== 'category' ? $description !== '' : (!empty($global['desc_enabled']) && $description !== ''),
'filter_bar' => !empty($global['filter_bar']) || $ajax_category_enabled || $ajax_tag_enabled || $ajax_topics_enabled || $orderby_enabled || !empty($custom_filter['custom_enabled']),
'post_count' => !empty($global['post_count']),
'orderby_enabled' => $orderby_enabled,
'orderby' => $orderby,
'orderby_option'=> $orderby_option,
'ajax_category_enabled' => $ajax_category_enabled,
'ajax_tag_enabled' => $ajax_tag_enabled,
'ajax_topics_enabled' => $ajax_topics_enabled,
'ajax_category' => $ajax_category,
'ajax_tag' => $ajax_tag,
'ajax_topics' => $ajax_topics,
'custom_filter_enabled' => !empty($custom_filter['custom_enabled']),
'custom_filter_keys' => $custom_filter['custom_filters'] ?? 'all',
'custom_dimensions' => is_array($filter_all['custom_filter'] ?? null) ? $filter_all['custom_filter'] : [],
@@ -191,6 +257,13 @@ class TermMeta {
$term = get_term_by('slug', $slug, 'post_tag');
}
}
} elseif ($query->is_tax('topics')) {
$path = trim((string) ($query->get('topics') ?: $query->get('term')), '/');
$segments = explode('/', $path);
$slug = sanitize_title((string) end($segments));
if ($slug !== '') {
$term = get_term_by('slug', $slug, 'topics');
}
}
if (!$term instanceof \WP_Term) {
@@ -245,11 +318,15 @@ class TermMeta {
* Render fields for "Add new term" form.
*/
public function render_add_fields(string $taxonomy): void {
wp_nonce_field('jio_term_meta', 'jio_term_meta_nonce');
?>
<div class="form-field">
<label for="jio_term_cover_url"><?php esc_html_e('封面图 URL', 'jio'); ?></label>
<input type="url" name="jio_term_meta[cover_url]" id="jio_term_cover_url" value="" placeholder="https://...">
<p class="description"><?php esc_html_e('分类/标签页顶部封面图,留空则不显示。', 'jio'); ?></p>
<?php if ($taxonomy === 'topics') : ?>
<button type="button" class="button" data-jio-topic-cover-select><?php esc_html_e('上传/添加图像', 'jio'); ?></button>
<?php endif; ?>
<p class="description"><?php esc_html_e('归档页顶部封面图,留空则使用默认封面。', 'jio'); ?></p>
</div>
<?php
}
@@ -259,19 +336,23 @@ class TermMeta {
*/
public function render_edit_fields(\WP_Term $term): void {
$meta = self::get_meta($term->term_id);
wp_nonce_field('jio_term_meta', 'jio_term_meta_nonce');
?>
<tr class="form-field">
<th scope="row"><label for="jio_term_cover_url"><?php esc_html_e('封面图 URL', 'jio'); ?></label></th>
<td>
<input type="url" name="jio_term_meta[cover_url]" id="jio_term_cover_url" value="<?php echo esc_url($meta['cover_url']); ?>" class="regular-text" placeholder="https://...">
<p class="description"><?php esc_html_e('分类/标签页顶部封面图,留空则不显示。', 'jio'); ?></p>
<?php if ($term->taxonomy === 'topics') : ?>
<button type="button" class="button" data-jio-topic-cover-select><?php esc_html_e('上传/添加图像', 'jio'); ?></button>
<?php endif; ?>
<p class="description"><?php esc_html_e('归档页顶部封面图,留空则使用默认封面。', 'jio'); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row"><label for="jio_term_desc"><?php esc_html_e('自定义简介', 'jio'); ?></label></th>
<td>
<textarea name="jio_term_meta[description]" id="jio_term_desc" rows="3" class="large-text"><?php echo esc_textarea($meta['description']); ?></textarea>
<p class="description"><?php esc_html_e('覆盖默认的分类/标签描述。', 'jio'); ?></p>
<p class="description"><?php esc_html_e('覆盖默认的术语描述。', 'jio'); ?></p>
</td>
</tr>
<tr class="form-field">
@@ -284,10 +365,6 @@ class TermMeta {
<label>
<input type="checkbox" name="jio_term_meta[show_sidebar]" value="1" <?php checked($meta['show_sidebar'] === '1'); ?>>
<?php esc_html_e('强制显示侧栏', 'jio'); ?>
</label><br>
<label>
<input type="checkbox" name="jio_term_meta[show_featured]" value="1" <?php checked(!empty($meta['show_featured'])); ?>>
<?php esc_html_e('显示推荐文章位', 'jio'); ?>
</label>
</td>
</tr>
@@ -301,17 +378,20 @@ class TermMeta {
if (!isset($_POST['jio_term_meta']) || !is_array($_POST['jio_term_meta'])) {
return;
}
$nonce = isset($_POST['jio_term_meta_nonce']) ? sanitize_text_field(wp_unslash((string) $_POST['jio_term_meta_nonce'])) : '';
if ($nonce === '' || !wp_verify_nonce($nonce, 'jio_term_meta')) {
return;
}
if (!current_user_can('manage_categories')) {
return;
}
$raw = $_POST['jio_term_meta'];
$raw = wp_unslash($_POST['jio_term_meta']);
$sanitized = [
'cover_url' => isset($raw['cover_url']) ? esc_url_raw($raw['cover_url']) : '',
'description' => isset($raw['description']) ? sanitize_textarea_field($raw['description']) : '',
'show_sidebar' => isset($raw['show_sidebar']) ? '1' : '',
'show_header' => isset($raw['show_header']) ? true : false,
'show_featured' => isset($raw['show_featured']) ? true : false,
];
update_term_meta($term_id, self::META_KEY, $sanitized);
+2
View File
@@ -51,6 +51,7 @@ trait DisplayPageGroups {
'home',
'category',
'tag',
'topics',
'author',
'user_center',
'single',
@@ -206,6 +207,7 @@ trait DisplayPageGroups {
'category_recommend' => self::get_category_recommend_settings(),
'category' => self::get_category_page_settings(),
'tag' => self::get_tag_page_settings(),
'topics' => self::get_topics_page_settings(),
'user_home' => self::get_user_home_settings(),
'other' => self::get_other_page_settings(),
];
+38 -2
View File
@@ -42,6 +42,7 @@ trait FacadeBulk {
$orderby = sanitize_key((string) (($orderby_option['lists'][0] ?? 'modified')));
$ajax_category_enabled = !empty($settings['ajax_list_cat_cat']);
$ajax_tag_enabled = !empty($settings['ajax_list_cat_tag']);
$ajax_topics_enabled = !empty($settings['ajax_list_cat_topics']);
$orderby_enabled = !empty($settings['cat_orderby_s']);
$post_count = !empty($settings['cat_post_count_s']);
@@ -51,7 +52,7 @@ trait FacadeBulk {
'desc_enabled' => true,
'default_cover' => esc_url_raw((string) ($settings['cat_default_cover'] ?? '')),
'list_mode' => 'grid',
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $orderby_enabled || $post_count,
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $ajax_topics_enabled || $orderby_enabled || $post_count,
'sidebar' => true,
'per_page' => max(1, (int) get_option('posts_per_page', 10)),
'post_count' => $post_count,
@@ -60,8 +61,10 @@ trait FacadeBulk {
'orderby_option' => $orderby_option,
'ajax_category_enabled' => $ajax_category_enabled,
'ajax_tag_enabled' => $ajax_tag_enabled,
'ajax_topics_enabled' => $ajax_topics_enabled,
'ajax_category' => is_array($settings['ajax_list_option_cat_cat'] ?? null) ? $settings['ajax_list_option_cat_cat'] : [],
'ajax_tag' => is_array($settings['ajax_list_option_cat_tag'] ?? null) ? $settings['ajax_list_option_cat_tag'] : [],
'ajax_topics' => is_array($settings['ajax_list_option_cat_topics'] ?? null) ? $settings['ajax_list_option_cat_topics'] : [],
];
}
@@ -78,6 +81,7 @@ trait FacadeBulk {
$orderby = sanitize_key((string) (($orderby_option['lists'][0] ?? 'modified')));
$ajax_category_enabled = !empty($settings['ajax_list_tag_cat']);
$ajax_tag_enabled = !empty($settings['ajax_list_tag_tag']);
$ajax_topics_enabled = !empty($settings['ajax_list_tag_topics']);
$orderby_enabled = !empty($settings['tag_orderby_s']);
$post_count = !empty($settings['tag_post_count_s']);
@@ -89,17 +93,49 @@ trait FacadeBulk {
'sidebar' => true,
'per_page' => max(1, (int) get_option('posts_per_page', 10)),
'post_count' => $post_count,
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $orderby_enabled || $post_count,
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $ajax_topics_enabled || $orderby_enabled || $post_count,
'orderby' => $orderby,
'orderby_enabled'=> $orderby_enabled,
'orderby_option' => $orderby_option,
'ajax_category_enabled' => $ajax_category_enabled,
'ajax_tag_enabled' => $ajax_tag_enabled,
'ajax_topics_enabled' => $ajax_topics_enabled,
'ajax_category' => is_array($settings['ajax_list_option_tag_cat'] ?? null) ? $settings['ajax_list_option_tag_cat'] : [],
'ajax_tag' => is_array($settings['ajax_list_option_tag_tag'] ?? null) ? $settings['ajax_list_option_tag_tag'] : [],
'ajax_topics' => is_array($settings['ajax_list_option_tag_topics'] ?? null) ? $settings['ajax_list_option_tag_topics'] : [],
];
}
public static function get_topics_page_settings(): array {
$settings = self::read_domain('page_display', 'topics');
$orderby_option = self::normalize_page_orderby_option($settings['topics_orderby_option'] ?? []);
$ajax_category_enabled = !empty($settings['ajax_list_topics_cat']);
$ajax_tag_enabled = !empty($settings['ajax_list_topics_tag']);
$ajax_topics_enabled = !array_key_exists('ajax_list_topics_topics', $settings) || !empty($settings['ajax_list_topics_topics']);
$orderby_enabled = !array_key_exists('topics_orderby_s', $settings) || !empty($settings['topics_orderby_s']);
$post_count = !array_key_exists('topics_post_count_s', $settings) || !empty($settings['topics_post_count_s']);
return [
'header_style' => 'cover',
'cover_enabled' => true,
'default_cover' => esc_url_raw((string) ($settings['topics_default_cover'] ?? get_template_directory_uri() . '/assets/img/user_t.jpg')),
'list_mode' => 'grid',
'sidebar' => false,
'per_page' => max(1, (int) get_option('posts_per_page', 10)),
'post_count' => $post_count,
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $ajax_topics_enabled || $orderby_enabled,
'orderby' => sanitize_key((string) ($orderby_option['lists'][0] ?? 'modified')),
'orderby_enabled' => $orderby_enabled,
'orderby_option' => $orderby_option,
'ajax_category_enabled' => $ajax_category_enabled,
'ajax_tag_enabled' => $ajax_tag_enabled,
'ajax_topics_enabled' => $ajax_topics_enabled,
'ajax_category' => is_array($settings['ajax_list_option_topics_cat'] ?? null) ? $settings['ajax_list_option_topics_cat'] : [],
'ajax_tag' => is_array($settings['ajax_list_option_topics_tag'] ?? null) ? $settings['ajax_list_option_topics_tag'] : [],
'ajax_topics' => is_array($settings['ajax_list_option_topics_topics'] ?? null) ? $settings['ajax_list_option_topics_topics'] : [],
];
}
/** @return array{header_style:string,header_cover_img:string} */
+11 -4
View File
@@ -27,11 +27,11 @@ class PostDisplay {
$card_style = $card_style === 'style3' ? 'style3' : 'null';
$sticky_pages = is_array($settings['sticky_s'] ?? null)
? array_values(array_intersect(array_map('sanitize_key', $settings['sticky_s']), ['home', 'cat', 'tag']))
? array_values(array_intersect(array_map('sanitize_key', $settings['sticky_s']), ['home', 'cat', 'tag', 'topics']))
: ['home', 'cat'];
$badge_types = is_array($settings['list_badge_show'] ?? null)
? array_values(array_intersect(array_map('sanitize_key', $settings['list_badge_show']), ['pay', 'cat', 'tag']))
: ['pay', 'cat', 'tag'];
? array_values(array_intersect(array_map('sanitize_key', $settings['list_badge_show']), ['pay', 'cat', 'tag', 'topics']))
: ['pay', 'tag', 'topics', 'cat'];
$card_cat_ids = is_array($settings['list_card_cat'] ?? null)
? array_values(array_filter(array_map('intval', $settings['list_card_cat'])))
: [];
@@ -58,6 +58,7 @@ class PostDisplay {
'list_show_type' => in_array($list_show_type, ['separate', 'no_margin'], true) ? $list_show_type : 'separate',
'list_card_home' => !empty($settings['list_card_home']),
'list_card_tag' => !empty($settings['list_card_tag']),
'list_card_topics' => !empty($settings['list_card_topics']),
'list_card_author' => !empty($settings['list_card_author']),
'list_card_cat' => $card_cat_ids,
'list_list_option' => [
@@ -104,7 +105,7 @@ class PostDisplay {
$settings = Settings::get_all();
return [
'custom_filter_show' => is_array($settings['custom_filter_show'] ?? null) ? array_values(array_intersect(array_map('sanitize_key', $settings['custom_filter_show']), ['cat', 'tag'])) : [],
'custom_filter_show' => is_array($settings['custom_filter_show'] ?? null) ? array_values(array_intersect(array_map('sanitize_key', $settings['custom_filter_show']), ['cat', 'tag', 'topics'])) : [],
'custom_filter' => self::sanitize_custom_filter_defs($settings['custom_filter'] ?? []),
'cat_custom_filter' => self::sanitize_cat_custom_filter($settings['cat_custom_filter'] ?? []),
];
@@ -132,6 +133,8 @@ class PostDisplay {
'cat_lists' => $rule['cat_lists'],
'tag_s' => $rule['tag_s'],
'tag_lists' => $rule['tag_lists'],
'topics_s' => $rule['topics_s'],
'topics_lists' => $rule['topics_lists'],
'orderby_s' => $rule['orderby_s'],
'orderby_lists' => $rule['orderby_lists'],
],
@@ -373,6 +376,8 @@ class PostDisplay {
'allowed_cat_ids' => is_array($settings['post_article_cat'] ?? null) ? array_values(array_filter(array_map('intval', $settings['post_article_cat']))) : [],
'default_user_id' => max(0, (int) ($settings['post_article_user'] ?? 1)),
'allow_tags' => !isset($settings['post_article_tag']) || !empty($settings['post_article_tag']),
'allow_topics' => !empty($settings['post_article_topics_s']),
'allowed_topic_ids' => is_array($settings['post_article_topics'] ?? null) ? array_values(array_filter(array_map('absint', $settings['post_article_topics']))) : [],
'custom_filter_s' => !isset($settings['post_article_custom_filter_s']) || !empty($settings['post_article_custom_filter_s']),
'pay_free_s' => !isset($settings['post_article_pay_free_s']) || !empty($settings['post_article_pay_free_s']),
'pay_price_min' => max(0, (int) ($cash_limit['min'] ?? 0)),
@@ -539,6 +544,8 @@ class PostDisplay {
'cat_lists' => self::sanitize_filter_option_lists($item['cat_lists'] ?? []),
'tag_s' => !empty($item['tag_s']),
'tag_lists' => self::sanitize_filter_option_lists($item['tag_lists'] ?? []),
'topics_s' => !empty($item['topics_s']),
'topics_lists' => self::sanitize_filter_option_lists($item['topics_lists'] ?? []),
'orderby_s' => !empty($item['orderby_s']),
'orderby_lists' => self::sanitize_filter_orderby_lists($item['orderby_lists'] ?? []),
'custom_filters' => is_array($item['custom_filters'] ?? null)
+3 -3
View File
@@ -108,7 +108,7 @@ class ThemeSearch {
$items = preg_split('/[,,\s]+/', (string) $raw) ?: [];
}
$items = array_map(static fn($k) => trim(sanitize_key((string) $k)), $items);
$valid = ['title', 'content', 'excerpt', 'category', 'post_tag', 'coment', 'user'];
$valid = ['title', 'content', 'excerpt', 'category', 'post_tag', 'topics', 'coment', 'user'];
return array_values(array_intersect(array_filter(array_unique($items)), $valid));
}
@@ -175,7 +175,7 @@ class ThemeSearch {
$terms = [(string) $query->get('s')];
}
$groups = [];
$taxonomies = ['post_tag', 'category'];
$taxonomies = ['post_tag', 'category', 'topics'];
foreach ($terms as $term) {
$like = (!empty($query->query_vars['exact']) ? '' : '%') . $wpdb->esc_like((string) $term) . (!empty($query->query_vars['exact']) ? '' : '%');
$parts = [];
@@ -212,7 +212,7 @@ class ThemeSearch {
if (in_array('user', $keys, true)) {
$join .= " LEFT JOIN {$wpdb->users} AS jio_search_users ON ({$wpdb->posts}.post_author = jio_search_users.ID)";
}
if (array_intersect(['post_tag', 'category'], $keys)) {
if (array_intersect(['post_tag', 'category', 'topics'], $keys)) {
$join .= " LEFT JOIN {$wpdb->term_relationships} AS jio_search_tr ON ({$wpdb->posts}.ID = jio_search_tr.object_id)";
$join .= " LEFT JOIN {$wpdb->term_taxonomy} AS jio_search_tt ON (jio_search_tr.term_taxonomy_id = jio_search_tt.term_taxonomy_id)";
$join .= " LEFT JOIN {$wpdb->terms} AS jio_search_terms ON (jio_search_tt.term_id = jio_search_terms.term_id)";
+7 -4
View File
@@ -206,8 +206,11 @@ final class FormRenderer {
}
private static function terms(string $id, string $name, array $field, $value): void {
$taxonomy = (string) ($field['taxonomy'] ?? 'category');
$terms = get_terms(['taxonomy' => $taxonomy, 'hide_empty' => false, 'number' => 500, 'orderby' => 'name']);
$taxonomies = array_values(array_unique(array_filter(array_map('sanitize_key', (array) ($field['taxonomy'] ?? 'category')))));
if ($taxonomies === []) {
$taxonomies = ['category'];
}
$terms = get_terms(['taxonomy' => $taxonomies, 'hide_empty' => false, 'number' => 500, 'orderby' => 'name']);
if (is_wp_error($terms)) {
$terms = [];
}
@@ -216,7 +219,7 @@ final class FormRenderer {
foreach ($terms as $term) {
$by_id[(int) $term->term_id] = $term;
}
echo '<div class="jio-widget-terms" data-jio-terms="' . esc_attr($taxonomy) . '">';
echo '<div class="jio-widget-terms" data-jio-terms="' . esc_attr(implode(',', $taxonomies)) . '">';
echo '<input type="hidden" class="jio-widget-terms__value" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr(implode(',', $selected)) . '">';
echo '<div class="jio-widget-terms__selected">';
foreach ($selected as $term_id) {
@@ -226,7 +229,7 @@ final class FormRenderer {
echo '<span class="jio-widget-terms__chip" data-term-id="' . esc_attr((string) $term_id) . '"><span class="dashicons dashicons-move" aria-hidden="true"></span>' . esc_html((string) $by_id[$term_id]->name) . '<button type="button" class="button-link-delete jio-widget-terms__remove" aria-label="' . esc_attr__('移除', 'jio') . '">×</button></span>';
}
echo '</div>';
echo '<input type="search" class="widefat jio-widget-terms__search" placeholder="' . esc_attr__('输入关键词筛选', 'jio') . '" aria-label="' . esc_attr__('筛选可选分类', 'jio') . '">';
echo '<input type="search" class="widefat jio-widget-terms__search" placeholder="' . esc_attr__('输入关键词筛选', 'jio') . '" aria-label="' . esc_attr__('筛选可选术语', 'jio') . '">';
echo '<div class="jio-widget-terms__options">';
foreach ($terms as $term) {
$term_id = (int) $term->term_id;
+8
View File
@@ -53,6 +53,14 @@ final class PostQuery {
if ($tags) {
$args['tag__in'] = $tags;
}
$topics = RendererHelpers::ids((string) ($config['topics'] ?? ''));
if ($topics) {
$args['tax_query'] = [[
'taxonomy' => 'topics',
'field' => 'term_id',
'terms' => $topics,
]];
}
$days = max(0, (int) ($config['limit_day'] ?? 0));
if ($days > 0) {
@@ -110,7 +110,7 @@ final class ContentRenderer {
if (!$term_ids) {
return '';
}
$terms = get_terms(['taxonomy' => 'category', 'include' => $term_ids, 'orderby' => 'include']);
$terms = get_terms(['taxonomy' => ['category', 'topics'], 'include' => $term_ids, 'orderby' => 'include']);
if (is_wp_error($terms) || !$terms) {
return '';
}
@@ -134,7 +134,7 @@ final class ContentRenderer {
$card = [
'type' => (string) ($instance['type'] ?? 'style-1'),
'class' => 'mb10',
'img' => jio_widget_term_cover_url((int) $term->term_id),
'img' => jio_widget_term_cover_url((int) $term->term_id, false, (string) $term->taxonomy),
'alt' => $name . '-' . $description,
'link' => ['url' => esc_url((string) $href), 'target' => $target_blank],
'text1' => $name,
@@ -179,8 +179,13 @@ final class ContentRenderer {
$animation = (string) ($instance['obs_animation'] ?? '');
$html = '';
foreach ($term_ids as $key => $term_id) {
$term = get_term((int) $term_id);
if (!$term instanceof \WP_Term || !in_array($term->taxonomy, ['category', 'topics'], true)) {
continue;
}
$card = jio_widget_term_aggregation([
'term_id' => (int) $term_id,
'taxonomy' => (string) $term->taxonomy,
'target_blank' => !empty($instance['target_blank']),
'orderby' => (string) ($instance['orderby'] ?? 'modified'),
'order' => (($instance['order'] ?? 'desc') === 'asc') ? 'ASC' : 'DESC',
+6 -3
View File
@@ -76,7 +76,7 @@ final class WidgetSchemas {
];
case 'tag_cloud':
return [
['id' => 'taxonomy', 'type' => 'select', 'label' => '分类法', 'default' => 'post_tag', 'options' => ['post_tag' => '标签', 'category' => '分类']],
['id' => 'taxonomy', 'type' => 'select', 'label' => '分类法', 'default' => 'post_tag', 'options' => ['post_tag' => '标签', 'category' => '分类', 'topics' => '专题']],
['id' => 'number', 'type' => 'spinner', 'label' => '数量', 'default' => 20, 'min' => 1, 'max' => 100],
['id' => 'orderby', 'type' => 'select', 'label' => '排序', 'default' => 'count', 'options' => ['count' => '内容数量', 'name' => '名称', 'term_id' => 'ID', 'rand' => '随机']],
['id' => 'show_count', 'type' => 'switcher', 'label' => '显示数量', 'default' => false],
@@ -249,6 +249,7 @@ final class WidgetSchemas {
return [
['id' => 'load_mode', 'type' => 'radio', 'label' => '模块加载方式', 'default' => 'ajax', 'options' => ['detail' => '直接加载', 'ajax' => 'ajax懒加载'], 'description' => 'ajax懒加载:当页面加载完后,根据用户需要在加载当前模块内容,可提高页面渲染效率'],
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids', 'description' => '限制请填写对应的ID,多个ID请用英文逗号隔开。如:1,2,3。支持负数进行排除,例如:-1,-2,-3。'],
['id' => 'topics', 'type' => 'text', 'label' => '专题限制', 'default' => '', 'sanitize' => 'ids', 'description' => '填写专题 ID,多个 ID 用逗号隔开'],
['id' => 'pay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => self::pay_types(), 'description' => '不做其它筛选'],
['id' => 'limit_day', 'type' => 'spinner', 'label' => '发布时间限制', 'default' => 0, 'min' => 0, 'step' => 5, 'unit' => '天', 'description' => '仅显示最近多少天发布的文章,为0则不限制'],
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
@@ -276,6 +277,7 @@ final class WidgetSchemas {
'fields' => [
['id' => 'title', 'type' => 'textarea', 'label' => '标题(必填)', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1], 'description' => '栏目显示的标题,支持HTML代码,注意代码规范'],
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids'],
['id' => 'topics', 'type' => 'text', 'label' => '专题限制', 'default' => '', 'sanitize' => 'ids'],
['id' => 'pay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => self::pay_types(), 'description' => '不做其它筛选'],
['id' => 'limit_day', 'type' => 'spinner', 'label' => '发布时间限制', 'default' => 0, 'min' => 0, 'step' => 5, 'unit' => '天', 'description' => '仅显示最近多少天发布的文章,为0则不限制'],
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
@@ -288,6 +290,7 @@ final class WidgetSchemas {
private static function oneline_posts_fields(): array {
return [
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids', 'description' => '限制请填写对应的ID,多个ID请用英文逗号隔开。如:1,2,3。支持负数进行排除,例如:-1,-2,-3。'],
['id' => 'topics', 'type' => 'text', 'label' => '专题限制', 'default' => '', 'sanitize' => 'ids', 'description' => '填写专题 ID,多个 ID 用逗号隔开'],
['id' => 'pay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => self::pay_types(), 'description' => '不做其它筛选'],
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序']],
@@ -312,7 +315,7 @@ final class WidgetSchemas {
private static function term_cards_fields(): array {
return array_merge(self::obs_animation_fields(), [
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类', 'taxonomy' => 'category', 'default' => [], 'description' => '选择并排序需要的分类,如选择的分类下没有封面则不会显示'],
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类、专题', 'taxonomy' => ['category', 'topics'], 'default' => [], 'description' => '选择并排序需要的分类、专题,如没有封面则不会显示'],
['id' => 'type', 'type' => 'radio', 'label' => '卡片样式', 'default' => 'style-1', 'options' => ['style-1' => '简单样式', 'style-2' => '样式二', 'style-3' => '样式三', 'style-4' => '样式四']],
['id' => 'height_scale', 'type' => 'spinner', 'label' => '卡片长宽比例', 'default' => 60, 'min' => 20, 'max' => 300, 'step' => 5, 'unit' => '%'],
['id' => 'pc_row', 'type' => 'button_set', 'label' => 'PC端单行排列数量', 'default' => 2, 'options' => self::row_options()],
@@ -325,7 +328,7 @@ final class WidgetSchemas {
private static function term_aggregation_fields(): array {
return array_merge(self::obs_animation_fields(), [
['id' => 'pc_row', 'type' => 'slider', 'label' => '单行布局', 'default' => 2, 'min' => 1, 'max' => 2, 'step' => 1, 'unit' => '个', 'description' => '请根据此模块放置位置的宽度合理调整单行数量'],
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类', 'taxonomy' => 'category', 'default' => [], 'description' => '选择并排序需要的分类,如选择的分类下没有文章则不会显示'],
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类、专题', 'taxonomy' => ['category', 'topics'], 'default' => [], 'description' => '选择并排序需要的分类、专题,如没有文章则不会显示'],
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => 'modified', 'options' => self::posts_orderby(), 'dependency' => ['term_id', '!=', '']],
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序'], 'dependency' => ['term_id', '!=', '']],
['id' => 'count', 'type' => 'spinner', 'label' => '最大文章数量', 'default' => 4, 'min' => 1, 'max' => 20, 'step' => 1, 'unit' => '篇', 'dependency' => ['term_id', '!=', ''], 'description' => '<div class="c-yellow">请确保所选的分类内的文章数量均超过此数量,否则会出现布局错位的问题!</div>'],