feat(extensions): 新增系统工具页面与前台投稿分片上传
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
<?php
|
||||
/**
|
||||
* 前端页面编辑(后台「扩展&增强 → 系统工具」的 admin_frontend_set)。
|
||||
*
|
||||
* 对齐参考主题 zib-frontend-set.php:管理员在前台通过悬浮按钮打开弹窗,
|
||||
* 快速修改当前文章/页面的标题、副标题、布局、计数、评论开关或分类术语的
|
||||
* 名称与描述;保存走白名单,行为与后台 PostMetaBox / wp_update_term 一致。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Frontend;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class PageAdminSet {
|
||||
|
||||
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_filter('aksrt_float_right_buttons', [$this, 'float_button'], 10, 2);
|
||||
add_action('wp_ajax_aksrt_frontend_set_modal', [$this, 'ajax_modal']);
|
||||
add_action('wp_ajax_aksrt_frontend_set_save', [$this, 'ajax_save']);
|
||||
add_action('wp_enqueue_scripts', [$this, 'enqueue_script']);
|
||||
}
|
||||
|
||||
/** 开启条件:设置开关 + 管理员 + 单页/术语页 + 有效对象(排除投稿与导航页模板)。 */
|
||||
public static function enabled(): bool {
|
||||
if (!class_exists('\AKSRT\Settings\ExtensionRuntime') || !is_user_logged_in()) {
|
||||
return false;
|
||||
}
|
||||
$settings = \AKSRT\Settings\ExtensionRuntime::get_system_tools_settings();
|
||||
if (empty($settings['admin_frontend_set']) || !current_user_can('manage_options')) {
|
||||
return false;
|
||||
}
|
||||
if (!get_queried_object_id() || (!is_single() && !is_page() && !is_category() && !is_tag() && !is_tax())) {
|
||||
return false;
|
||||
}
|
||||
if (is_page_template('templates/pages/page-submit.php') || is_page_template('templates/pages/page-posts-navs.php')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 悬浮按钮:注入右侧悬浮按钮栏(对齐参考主题 zib_admin_frontend_set_botton)。 */
|
||||
public function float_button($buttons, $is_mobile) {
|
||||
if (!self::enabled()) {
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
$url = add_query_arg(
|
||||
[
|
||||
'action' => 'aksrt_frontend_set_modal',
|
||||
'_wpnonce' => wp_create_nonce('aksrt_frontend_set'),
|
||||
],
|
||||
admin_url('admin-ajax.php')
|
||||
);
|
||||
|
||||
$button = '<a class="float-btn admin-set-btn" href="javascript:;"'
|
||||
. ' data-aksrt-modal="remote" data-aksrt-modal-cache="false" data-aksrt-modal-kind="admin-set"'
|
||||
. ' data-aksrt-modal-url="' . esc_url($url) . '"'
|
||||
. aksrt_float_btn_tooltip_attr(__('在此编辑此页面参数', 'aksrt'), (bool) $is_mobile)
|
||||
. '><i class="fa fas fa-cog"></i></a>';
|
||||
|
||||
return $button . (string) $buttons;
|
||||
}
|
||||
|
||||
/** 条件加载弹窗提交脚本。 */
|
||||
public function enqueue_script(): void {
|
||||
if (!self::enabled()) {
|
||||
return;
|
||||
}
|
||||
$src = get_template_directory() . '/assets/js/page-admin-set.js';
|
||||
wp_enqueue_script(
|
||||
'aksrt-page-admin-set',
|
||||
get_template_directory_uri() . '/assets/js/page-admin-set.js',
|
||||
[],
|
||||
file_exists($src) ? (string) filemtime($src) : null,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/** GET 弹窗内容(注入 aksrt-modal-runtime)。 */
|
||||
public function ajax_modal(): void {
|
||||
check_ajax_referer('aksrt_frontend_set', '_wpnonce');
|
||||
if (!self::enabled()) {
|
||||
wp_send_json_error(['msg' => __('权限不足', 'aksrt')]);
|
||||
}
|
||||
|
||||
$object_id = (int) get_queried_object_id();
|
||||
if (is_category() || is_tag() || is_tax()) {
|
||||
$html = $this->term_modal_html($object_id, (string) get_queried_object()->taxonomy);
|
||||
} else {
|
||||
$html = $this->post_modal_html($object_id, get_post_type($object_id) ?: 'post');
|
||||
}
|
||||
|
||||
wp_send_json_success(['html' => $html]);
|
||||
}
|
||||
|
||||
/** POST 保存:仅接受白名单字段。 */
|
||||
public function ajax_save(): void {
|
||||
check_ajax_referer('aksrt_frontend_set', '_wpnonce');
|
||||
if (!self::enabled()) {
|
||||
wp_send_json_error(['msg' => __('权限不足', 'aksrt')]);
|
||||
}
|
||||
|
||||
$object_id = isset($_POST['id']) ? absint($_POST['id']) : 0;
|
||||
if (!$object_id) {
|
||||
wp_send_json_error(['msg' => __('页面数据出错', 'aksrt')]);
|
||||
}
|
||||
|
||||
$type = isset($_POST['type']) ? sanitize_key((string) $_POST['type']) : '';
|
||||
if ($type === 'tax') {
|
||||
$taxonomy = isset($_POST['subtype']) ? sanitize_key((string) $_POST['subtype']) : '';
|
||||
$this->save_term($object_id, $taxonomy);
|
||||
} else {
|
||||
$this->save_post($object_id);
|
||||
}
|
||||
|
||||
wp_send_json_success(['msg' => __('保存成功,正在刷新页面', 'aksrt'), 'reload' => true]);
|
||||
}
|
||||
|
||||
private function save_post(int $post_id): void {
|
||||
$post = get_post($post_id);
|
||||
if (!$post) {
|
||||
wp_send_json_error(['msg' => __('未找到可用数据', 'aksrt')]);
|
||||
}
|
||||
if (!current_user_can('edit_post', $post_id)) {
|
||||
wp_send_json_error(['msg' => __('权限不足', 'aksrt')]);
|
||||
}
|
||||
|
||||
$postarr = ['ID' => $post_id];
|
||||
if (isset($_POST['post_title'])) {
|
||||
$title = sanitize_text_field(wp_unslash((string) $_POST['post_title']));
|
||||
if ($title !== '') {
|
||||
$postarr['post_title'] = $title;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['comments_open'])) {
|
||||
$postarr['comment_status'] = !empty($_POST['comments_open']) ? 'open' : 'closed';
|
||||
}
|
||||
if (count($postarr) > 1) {
|
||||
$result = wp_update_post($postarr, true);
|
||||
if (is_wp_error($result)) {
|
||||
wp_send_json_error(['msg' => $result->get_error_message()]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_POST['post_format']) && post_type_supports($post->post_type, 'post-formats')) {
|
||||
$format = sanitize_key((string) wp_unslash($_POST['post_format']));
|
||||
set_post_format($post_id, $format === 'standard' ? '' : $format);
|
||||
}
|
||||
|
||||
if ($post->post_type === 'post' && class_exists('\AKSRT\Core\PostMetaBox')) {
|
||||
$layout = isset($_POST['show_layout']) ? sanitize_key((string) wp_unslash($_POST['show_layout'])) : '';
|
||||
if ($layout !== '' && !in_array($layout, ['false', 'no_sidebar', 'sidebar_left', 'sidebar_right'], true)) {
|
||||
$layout = 'false';
|
||||
}
|
||||
|
||||
// 保留白名单之外的既有配置键(封面、视频等),仅覆盖弹窗提交的字段。
|
||||
// 复选框未勾选时浏览器不会提交键名,必须无条件写入两个开关。
|
||||
$meta = get_post_meta($post_id, \AKSRT\Core\PostMetaBox::META_KEY, true);
|
||||
$meta = is_array($meta) ? $meta : [];
|
||||
if ($layout !== '') {
|
||||
$meta['show_layout'] = $layout;
|
||||
}
|
||||
if (isset($_POST['subtitle'])) {
|
||||
$meta['subtitle'] = sanitize_text_field(wp_unslash((string) $_POST['subtitle']));
|
||||
}
|
||||
$meta['no_article-navs'] = !empty($_POST['no_article-navs']);
|
||||
$meta['article_maxheight_xz'] = !empty($_POST['article_maxheight_xz']);
|
||||
update_post_meta($post_id, \AKSRT\Core\PostMetaBox::META_KEY, $meta);
|
||||
|
||||
if (isset($_POST['views'])) {
|
||||
update_post_meta($post_id, \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY, max(0, (int) $_POST['views']));
|
||||
}
|
||||
if (isset($_POST['like'])) {
|
||||
update_post_meta($post_id, '_aksrt_like_count', max(0, (int) $_POST['like']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function save_term(int $term_id, string $taxonomy = ''): void {
|
||||
$term = $taxonomy !== '' ? get_term($term_id, $taxonomy) : get_term($term_id);
|
||||
if (!$term || is_wp_error($term)) {
|
||||
wp_send_json_error(['msg' => __('未找到可用数据', 'aksrt')]);
|
||||
}
|
||||
if (!current_user_can('manage_categories') && !current_user_can('manage_terms')) {
|
||||
wp_send_json_error(['msg' => __('权限不足', 'aksrt')]);
|
||||
}
|
||||
|
||||
$data = [];
|
||||
if (isset($_POST['term_name'])) {
|
||||
$name = sanitize_text_field(wp_unslash((string) $_POST['term_name']));
|
||||
if ($name !== '') {
|
||||
$data['name'] = $name;
|
||||
}
|
||||
}
|
||||
if (isset($_POST['term_description'])) {
|
||||
$data['description'] = sanitize_textarea_field(wp_unslash((string) $_POST['term_description']));
|
||||
}
|
||||
if (!$data) {
|
||||
return;
|
||||
}
|
||||
|
||||
$result = wp_update_term($term_id, (string) $term->taxonomy, $data);
|
||||
if (is_wp_error($result)) {
|
||||
wp_send_json_error(['msg' => $result->get_error_message()]);
|
||||
}
|
||||
}
|
||||
|
||||
private function post_modal_html(int $post_id, string $post_type): string {
|
||||
$meta = class_exists('\AKSRT\Core\PostMetaBox')
|
||||
? \AKSRT\Core\PostMetaBox::get_meta($post_id)
|
||||
: [];
|
||||
$views = (int) ($meta['views'] ?? 0);
|
||||
$like = (int) ($meta['like'] ?? 0);
|
||||
$is_single = is_single();
|
||||
|
||||
$html = '<div class="aksrt-admin-set">';
|
||||
$html .= '<p class="em09 muted-3-color mb10">' . esc_html(get_the_title($post_id)) . '</p>';
|
||||
|
||||
$html .= $this->text_field('post_title', __('标题', 'aksrt'), (string) get_the_title($post_id));
|
||||
|
||||
if ($is_single) {
|
||||
$html .= $this->text_field('subtitle', __('副标题', 'aksrt'), (string) ($meta['subtitle'] ?? ''));
|
||||
$layout = (string) ($meta['show_layout'] ?? 'false');
|
||||
$html .= '<div class="relative line-form mb10 modal-line-field"><select name="show_layout" class="line-form-input">'
|
||||
. '<option value="false"' . selected($layout, 'false', false) . '>' . esc_html__('跟随主题', 'aksrt') . '</option>'
|
||||
. '<option value="no_sidebar"' . selected($layout, 'no_sidebar', false) . '>' . esc_html__('无侧边栏', 'aksrt') . '</option>'
|
||||
. '<option value="sidebar_left"' . selected($layout, 'sidebar_left', false) . '>' . esc_html__('侧边栏靠左', 'aksrt') . '</option>'
|
||||
. '<option value="sidebar_right"' . selected($layout, 'sidebar_right', false) . '>' . esc_html__('侧边栏靠右', 'aksrt') . '</option>'
|
||||
. '</select><i class="line-form-line"></i></div>';
|
||||
$html .= $this->number_field('like', __('点赞数', 'aksrt'), $like);
|
||||
$html .= $this->number_field('views', __('阅读数', 'aksrt'), $views);
|
||||
$html .= $this->checkbox_field('no_article-navs', __('不显示目录树', 'aksrt'), !empty($meta['no_article-navs']));
|
||||
$html .= $this->checkbox_field('article_maxheight_xz', __('限制文章最大高度', 'aksrt'), !empty($meta['article_maxheight_xz']));
|
||||
}
|
||||
|
||||
$html .= '<div class="relative line-form mb10 modal-line-field"><select name="comments_open" class="line-form-input">'
|
||||
. '<option value="open"' . selected(comments_open($post_id), true, false) . '>' . esc_html__('开启评论', 'aksrt') . '</option>'
|
||||
. '<option value=""' . selected(comments_open($post_id), false, false) . '>' . esc_html__('关闭评论', 'aksrt') . '</option>'
|
||||
. '</select><i class="line-form-line"></i></div>';
|
||||
|
||||
$html .= $this->hidden_fields('post', $post_id, $post_type);
|
||||
$html .= '<div class="flex jsb mt10"><button type="button" class="but" data-aksrt-modal-close>' . esc_html__('取消', 'aksrt') . '</button>'
|
||||
. '<button type="submit" class="but jb-blue aksrt-admin-set-submit"><i class="fa fas fa-check"></i> ' . esc_html__('确认修改', 'aksrt') . '</button></div>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $this->wrap_form($html);
|
||||
}
|
||||
|
||||
private function term_modal_html(int $term_id, string $taxonomy): string {
|
||||
$term = get_term($term_id, $taxonomy);
|
||||
if (!$term || is_wp_error($term)) {
|
||||
return '<div class="aksrt-admin-set"><p class="muted-3-color">' . esc_html__('未找到可用数据', 'aksrt') . '</p></div>';
|
||||
}
|
||||
|
||||
$html = '<div class="aksrt-admin-set">';
|
||||
$html .= '<p class="em09 muted-3-color mb10">' . esc_html($term->name) . '</p>';
|
||||
$html .= $this->text_field('term_name', __('名称', 'aksrt'), (string) $term->name);
|
||||
$html .= $this->text_field('term_description', __('描述', 'aksrt'), (string) $term->description);
|
||||
$html .= $this->hidden_fields('tax', $term_id, $taxonomy);
|
||||
$html .= '<div class="flex jsb mt10"><button type="button" class="but" data-aksrt-modal-close>' . esc_html__('取消', 'aksrt') . '</button>'
|
||||
. '<button type="submit" class="but jb-blue aksrt-admin-set-submit"><i class="fa fas fa-check"></i> ' . esc_html__('确认修改', 'aksrt') . '</button></div>';
|
||||
$html .= '</div>';
|
||||
|
||||
return $this->wrap_form($html);
|
||||
}
|
||||
|
||||
private function wrap_form(string $inner): string {
|
||||
return '<form class="aksrt-admin-set-form">'
|
||||
. '<input type="hidden" name="action" value="aksrt_frontend_set_save">'
|
||||
. wp_nonce_field('aksrt_frontend_set', '_wpnonce', true, false)
|
||||
. $inner
|
||||
. '</form>';
|
||||
}
|
||||
|
||||
private function hidden_fields(string $type, int $id, string $subtype): string {
|
||||
return '<input type="hidden" name="type" value="' . esc_attr($type) . '">'
|
||||
. '<input type="hidden" name="subtype" value="' . esc_attr($subtype) . '">'
|
||||
. '<input type="hidden" name="id" value="' . esc_attr((string) $id) . '">';
|
||||
}
|
||||
|
||||
private function text_field(string $name, string $label, string $value): string {
|
||||
return '<div class="relative line-form mb10 modal-line-field"><input type="text" name="' . esc_attr($name) . '" value="' . esc_attr($value) . '" class="line-form-input">'
|
||||
. '<div class="scale-placeholder">' . esc_html($label) . '</div><i class="line-form-line"></i></div>';
|
||||
}
|
||||
|
||||
private function number_field(string $name, string $label, int $value): string {
|
||||
return '<div class="relative line-form mb10 modal-line-field"><input type="number" min="0" step="1" name="' . esc_attr($name) . '" value="' . esc_attr((string) $value) . '" class="line-form-input">'
|
||||
. '<div class="scale-placeholder">' . esc_html($label) . '</div><i class="line-form-line"></i></div>';
|
||||
}
|
||||
|
||||
private function checkbox_field(string $name, string $label, bool $checked): string {
|
||||
return '<div class="mb10 form-switch-label"><label><input type="checkbox" name="' . esc_attr($name) . '" value="1"' . checked($checked, true, false) . '> ' . esc_html($label) . '</label></div>';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user