feat(extensions): 新增系统工具页面与前台投稿分片上传
This commit is contained in:
@@ -14,10 +14,11 @@ class SchemaExtensions {
|
||||
'domain' => 'extensions',
|
||||
'title' => '扩展&增强',
|
||||
'sections' => [
|
||||
'ip' => SchemaExtensionsIP::build(),
|
||||
'audit' => SchemaExtensionsAudit::build(),
|
||||
'push' => SchemaExtensionsPush::build(),
|
||||
'security' => SchemaExtensionsSecurity::build(),
|
||||
'system' => SchemaExtensionsSystem::build(),
|
||||
'ip' => SchemaExtensionsIP::build(),
|
||||
'audit' => SchemaExtensionsAudit::build(),
|
||||
'push' => SchemaExtensionsPush::build(),
|
||||
'security' => SchemaExtensionsSecurity::build(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Admin\Schema;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class SchemaExtensionsSystem {
|
||||
|
||||
public static function build(): array {
|
||||
return [
|
||||
'key' => 'system',
|
||||
'title' => '系统工具',
|
||||
'version' => '1.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'optimize', 'title' => 'WordPress 优化', 'fields' => [
|
||||
['key' => 'hide_admin_bar', 'label' => '前台关闭顶部admin_bar', 'type' => 'switch', 'default' => true, 'help' => '开启后前台不显示WordPress顶部黑条'],
|
||||
['key' => 'remove_emoji', 'label' => '删除WordPress自带Emoji表情', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'remove_open_sans', 'label' => '禁用Google字体', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'remove_more_wp_head', 'label' => '清理多余的头部Meta标签', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'remove_dashboard_widgets', 'label' => 'WordPress 后台加速', 'type' => 'switch', 'default' => true, 'help' => '移除仪表盘的站点健康和WordPress活动等不必要功能,并禁止检测浏览器和PHP版本(这两个非常耗时),优化后台加载速度'],
|
||||
['key' => 'display_wp_update', 'label' => '禁止WordPress检测更新', 'type' => 'switch', 'default' => true, 'help' => '开启后将禁用WordPress核心、插件和主题的自动更新及更新检测'],
|
||||
]],
|
||||
['key' => 'upload', 'title' => '上传优化', 'fields' => [
|
||||
['key' => 'split_upload_s', 'label' => '大文件分片上传', 'type' => 'switch', 'default' => true, 'help' => '分片上传大文件,优化大文件上传效率(支持断点续传)'],
|
||||
['key' => 'split_minimum_size', 'label' => '分片上传最小文件', 'type' => 'number', 'default' => 20, 'min' => 4, 'max' => 100, 'unit' => 'M', 'dependsOn' => ['key' => 'split_upload_s', 'value' => true], 'help' => '大于此大小的文件才会分片上传,请根据服务器性能合理设置;较大文件直接上传会比较耗时且无法突破系统上传大小限制,分片上传可以有效解决此问题;前端上传大小限制在「功能权限 → 上传权限」中设置。注意:能否顺利上传大文件还取决于服务器性能、带宽、CDN等因素,如果网站使用了OSS/COS等第三方云储存,则不建议在网站上传大文件'],
|
||||
['key' => 'newfilename', 'label' => '上传文件自动重命名', 'type' => 'switch', 'default' => false, 'help' => '上传文件时系统会查询重名文件,开启此功能后可减少重名文件,从而提高上传效率'],
|
||||
['key' => 'newfilename_type', 'label' => '重命名格式', 'type' => 'radio', 'default' => 'time', 'dependsOn' => ['key' => 'newfilename', 'value' => true], 'options' => [
|
||||
['label' => '随机字串符', 'value' => 'random'],
|
||||
['label' => '时间+原文件名', 'value' => 'time'],
|
||||
]],
|
||||
['key' => 'disabled_large_thumbnail', 'label' => '禁止WP大尺寸缩略图裁剪', 'type' => 'switch', 'default' => true, 'help' => '开启后将禁用WordPress自动裁剪medium_large、1536x1536、2048x2048的缩略图,可减少服务器硬盘压力,同时提高上传效率;如果您使用的插件必须要这些尺寸的缩略图,或使用了云储存的图片处理功能,则请关闭'],
|
||||
]],
|
||||
['key' => 'editor', 'title' => '编辑器与显示', 'fields' => [
|
||||
['key' => 'admin_user_del_fields', 'label' => '关闭后台用户编辑多余的选项', 'type' => 'switch', 'default' => true, 'help' => '开启后在后台编辑用户资料时将不显示无用的多余选项'],
|
||||
['key' => 'admin_frontend_set', 'label' => '前端页面编辑', 'type' => 'switch', 'default' => true, 'help' => '管理员可在前台快速修改页面、文章参数'],
|
||||
['key' => 'close_gutenberg', 'label' => '禁用古腾堡编辑器', 'type' => 'switch', 'default' => false, 'help' => '后台文章编辑器将退回使用经典编辑器;禁用后主题的编辑器增强功能将不能使用,请酌情禁用!'],
|
||||
['key' => 'disabled_autoembed', 'label' => '禁用Auto Embed', 'type' => 'switch', 'default' => true, 'help' => 'Auto Embed是WordPress自动检测并嵌入链接的一种功能,主要适配海外媒体网站,国内几乎用不上,建议禁用,可大幅度提高页面渲染速度'],
|
||||
['key' => 'mce_float_toolbar', 'label' => '经典编辑器增强浮动工具栏', 'type' => 'checkbox', 'default' => ['pc_s', 'm_s'], 'sanitize_type' => 'array', 'options' => [
|
||||
['label' => 'PC端开启', 'value' => 'pc_s'],
|
||||
['label' => '移动端开启', 'value' => 'm_s'],
|
||||
], 'help' => '开启后前端编辑器会显示浮动的工具栏,更加方便书写'],
|
||||
['key' => 'time_ago_s', 'label' => '倒计时显示', 'type' => 'switch', 'default' => true, 'help' => '时间格式化为:X分钟前,X小时前,X天前....'],
|
||||
['key' => 'time_format', 'label' => '自定义时间格式', 'type' => 'input', 'default' => 'n月j日 H:i', 'dependsOn' => ['key' => 'time_ago_s', 'operator' => 'falsy'], 'help' => '关闭倒计时显示后生效,时间格式接受标准PHP时间格式,请注意控制长度!'],
|
||||
]],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -131,13 +131,6 @@ class SchemaPermissions {
|
||||
['key' => 'val', 'label' => '格式', 'type' => 'textarea', 'default' => '', 'rows' => 1, 'placeholder' => '如:svg=image/svg+xml,mp3=audio/mpeg'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'behavior', 'title' => '上传行为', 'fields' => [
|
||||
['key' => 'newfilename', 'label' => '上传文件自动重命名', 'type' => 'switch', 'default' => false, 'help' => '开启后上传文件按规则重命名,减少重名文件查询,提高上传效率。'],
|
||||
['key' => 'newfilename_type', 'label' => '重命名格式', 'type' => 'radio', 'default' => 'time', 'dependsOn' => ['key' => 'newfilename', 'value' => true], 'options' => [
|
||||
['label' => '时间 + 原文件名', 'value' => 'time'],
|
||||
['label' => '随机字符串', 'value' => 'random'],
|
||||
]],
|
||||
]],
|
||||
],
|
||||
],
|
||||
'moderation' => [
|
||||
|
||||
@@ -17,6 +17,7 @@ class FrontSubmit {
|
||||
use FrontSubmitPay;
|
||||
use FrontSubmitFeatured;
|
||||
use FrontSubmitMedia;
|
||||
use FrontSubmitChunk;
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
@@ -93,6 +94,8 @@ class FrontSubmit {
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->register_chunk_routes();
|
||||
}
|
||||
|
||||
public function can_upload_attachment(\WP_REST_Request $request): bool {
|
||||
@@ -128,15 +131,26 @@ class FrontSubmit {
|
||||
}
|
||||
|
||||
public function rest_upload_attachment(\WP_REST_Request $request) {
|
||||
$kind = (string) $request->get_param('kind');
|
||||
$user_id = get_current_user_id();
|
||||
$files = $request->get_file_params();
|
||||
$file = $files['file'] ?? null;
|
||||
$kind = (string) $request->get_param('kind');
|
||||
$files = $request->get_file_params();
|
||||
$file = $files['file'] ?? null;
|
||||
|
||||
if (!is_array($file)) {
|
||||
return new \WP_Error('aksrt_upload_missing_file', __('请选择要上传的文件。', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
return $this->persist_uploaded_file($kind, $file, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验并把一个上传文件落库为附件;整传与分片合并共用同一条管线。
|
||||
*
|
||||
* @param string $kind image|video|resource
|
||||
* @param array $file $_FILES 风格数组(name/tmp_name/size/error)
|
||||
* @param bool $sideload true 时文件不是 HTTP 直传(分片合并产物),用 sideload 动作保存
|
||||
* @return \WP_REST_Response|\WP_Error
|
||||
*/
|
||||
public function persist_uploaded_file(string $kind, array $file, bool $sideload = false) {
|
||||
$max_size = self::upload_limit_bytes($kind);
|
||||
$upload_error = (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE);
|
||||
if ($upload_error === UPLOAD_ERR_INI_SIZE || $upload_error === UPLOAD_ERR_FORM_SIZE) {
|
||||
@@ -156,7 +170,7 @@ class FrontSubmit {
|
||||
return new \WP_Error('aksrt_upload_failed', __('服务器无法保存上传文件。', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
$size = (int) ($file['size'] ?? 0);
|
||||
$size = (int) ($file['size'] ?? 0);
|
||||
|
||||
if ($size <= 0 || $size > $max_size) {
|
||||
return new \WP_Error(
|
||||
@@ -197,15 +211,18 @@ class FrontSubmit {
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
}
|
||||
|
||||
// 合并产物不是 HTTP 直传文件,走 sideload 动作(copy 而非 move_uploaded_file)。
|
||||
$overrides = $sideload ? ['test_form' => false, 'action' => 'wp_handle_sideload'] : ['test_form' => false];
|
||||
|
||||
$_FILES['aksrt_front_submit_file'] = $file;
|
||||
$attachment_id = media_handle_upload('aksrt_front_submit_file', 0, [], ['test_form' => false]);
|
||||
$attachment_id = media_handle_upload('aksrt_front_submit_file', 0, [], $overrides);
|
||||
unset($_FILES['aksrt_front_submit_file']);
|
||||
if (is_wp_error($attachment_id)) {
|
||||
return new \WP_Error('aksrt_upload_failed', $attachment_id->get_error_message(), ['status' => 400]);
|
||||
}
|
||||
|
||||
$attachment_id = (int) $attachment_id;
|
||||
wp_update_post(['ID' => $attachment_id, 'post_author' => $user_id]);
|
||||
wp_update_post(['ID' => $attachment_id, 'post_author' => get_current_user_id()]);
|
||||
update_post_meta($attachment_id, '_aksrt_front_submit_media', $kind);
|
||||
|
||||
$attachment = $this->prepare_attachment_response($attachment_id);
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* 前台大文件分片上传(后台「扩展&增强 → 系统工具」的 split_upload_s / split_minimum_size)。
|
||||
*
|
||||
* 行为对齐参考主题 action/media.php 的 user_split_upload / user_split_upload_merge /
|
||||
* user_split_uploaded_chunk 协议:2MB 一片、断点续传(已传分片按序跳过)、合并产物
|
||||
* 经 sideload 动作重新走 persist_uploaded_file() 标准校验与落库管线。
|
||||
*/
|
||||
trait FrontSubmitChunk {
|
||||
|
||||
/** 每个分片的固定大小(参考主题 split_chunk_size = 2MB)。 */
|
||||
const CHUNK_SIZE_BYTES = 2 * 1048576;
|
||||
|
||||
public function register_chunk_routes(): void {
|
||||
$kind_arg = [
|
||||
'validate_callback' => static function ($value): bool {
|
||||
return in_array((string) $value, ['image', 'video', 'resource'], true);
|
||||
},
|
||||
];
|
||||
|
||||
register_rest_route('aksrt/v1', '/front-submit/upload-chunk/(?P<kind>image|video|resource)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_upload_chunk'],
|
||||
'permission_callback' => [$this, 'can_upload_attachment'],
|
||||
'args' => [
|
||||
'kind' => $kind_arg,
|
||||
'chunk' => ['default' => 0, 'sanitize_callback' => 'absint'],
|
||||
'chunks' => ['default' => 0, 'sanitize_callback' => 'absint'],
|
||||
'name' => ['default' => '', 'sanitize_callback' => 'sanitize_file_name'],
|
||||
'size' => ['default' => 0, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
|
||||
register_rest_route('aksrt/v1', '/front-submit/upload-merge/(?P<kind>image|video|resource)', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_upload_chunk_merge'],
|
||||
'permission_callback' => [$this, 'can_upload_attachment'],
|
||||
'args' => [
|
||||
'kind' => $kind_arg,
|
||||
'chunks' => ['default' => 0, 'sanitize_callback' => 'absint'],
|
||||
'name' => ['default' => '', 'sanitize_callback' => 'sanitize_file_name'],
|
||||
'size' => ['default' => 0, 'sanitize_callback' => 'absint'],
|
||||
],
|
||||
]);
|
||||
|
||||
register_rest_route('aksrt/v1', '/front-submit/upload-status/(?P<kind>image|video|resource)', [
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [$this, 'rest_upload_chunk_status'],
|
||||
'permission_callback' => [$this, 'can_upload_attachment'],
|
||||
'args' => [
|
||||
'kind' => $kind_arg,
|
||||
'chunks' => ['default' => 0, 'sanitize_callback' => 'absint'],
|
||||
'name' => ['default' => '', 'sanitize_callback' => 'sanitize_file_name'],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传单个分片;index 为 0 基,同时累加校验总大小不超上限。
|
||||
*/
|
||||
public function rest_upload_chunk(\WP_REST_Request $request) {
|
||||
$file = $request->get_file_params()['file'] ?? null;
|
||||
if (!is_array($file) || empty($file['tmp_name']) || (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||||
return new \WP_Error('aksrt_chunk_missing_file', __('上传信息错误,请重新选择文件', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$params = $this->chunk_params($request, true);
|
||||
if (is_wp_error($params)) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
$max_size = self::upload_limit_bytes($params['kind']);
|
||||
if ($params['size'] <= 0 || $params['size'] > $max_size) {
|
||||
return new \WP_Error('aksrt_upload_too_large', sprintf(__('文件大小不能超过 %s。', 'aksrt'), size_format($max_size)), ['status' => 413]);
|
||||
}
|
||||
|
||||
$chunk_bytes = (int) @filesize((string) $file['tmp_name']);
|
||||
if ($chunk_bytes <= 0 || $chunk_bytes > self::CHUNK_SIZE_BYTES) {
|
||||
return new \WP_Error('aksrt_chunk_invalid_size', __('分片大小异常,请重新上传', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
$temp_dir = $this->chunk_temp_dir();
|
||||
if ($params['chunk'] === 0) {
|
||||
$this->chunk_temp_cleanup($temp_dir);
|
||||
}
|
||||
|
||||
$existing = $this->chunk_part_paths($temp_dir, $params['key']);
|
||||
$uploaded = 0;
|
||||
foreach ($existing as $path) {
|
||||
$uploaded += (int) @filesize($path);
|
||||
}
|
||||
if ($uploaded + $chunk_bytes > $max_size) {
|
||||
return new \WP_Error('aksrt_upload_too_large', sprintf(__('文件大小不能超过 %s。', 'aksrt'), size_format($max_size)), ['status' => 413]);
|
||||
}
|
||||
|
||||
$target = $temp_dir . '/' . $this->chunk_part_name($params['key'], $params['chunk']);
|
||||
$moved = @move_uploaded_file((string) $file['tmp_name'], $target);
|
||||
if (!$moved) {
|
||||
$moved = @copy((string) $file['tmp_name'], $target);
|
||||
@unlink((string) $file['tmp_name']);
|
||||
}
|
||||
if (!$moved) {
|
||||
return new \WP_Error('aksrt_chunk_save_failed', __('分片保存失败,请重新上传', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
return new \WP_REST_Response([
|
||||
'success' => true,
|
||||
'chunk' => $params['chunk'],
|
||||
'chunks' => $params['chunks'],
|
||||
'uploaded_size' => $uploaded + $chunk_bytes,
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断点续传:返回已落盘的分片序号,前端按序跳过。
|
||||
*/
|
||||
public function rest_upload_chunk_status(\WP_REST_Request $request) {
|
||||
$params = $this->chunk_params($request);
|
||||
if (is_wp_error($params)) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
$parts = $this->chunk_part_paths($this->chunk_temp_dir(), $params['key']);
|
||||
$uploaded = [];
|
||||
foreach ($parts as $path) {
|
||||
$uploaded[] = $this->chunk_index_from_name((string) $path);
|
||||
}
|
||||
|
||||
return new \WP_REST_Response([
|
||||
'success' => true,
|
||||
'uploaded' => $uploaded,
|
||||
'uploaded_count' => count($uploaded),
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并分片并按标准管线落库;成功或失败后都清理分片。
|
||||
*/
|
||||
public function rest_upload_chunk_merge(\WP_REST_Request $request) {
|
||||
$params = $this->chunk_params($request);
|
||||
if (is_wp_error($params)) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
$max_size = self::upload_limit_bytes($params['kind']);
|
||||
$temp_dir = $this->chunk_temp_dir();
|
||||
$merge_path = $temp_dir . '/' . $this->chunk_part_name($params['key'], 'merge');
|
||||
|
||||
$fp = @fopen($merge_path, 'wb');
|
||||
if (!$fp) {
|
||||
return new \WP_Error('aksrt_chunk_merge_failed', __('分片合并失败,请重新上传', 'aksrt'), ['status' => 500]);
|
||||
}
|
||||
|
||||
$missing = 0;
|
||||
for ($i = 0; $i < $params['chunks']; $i++) {
|
||||
$part = $temp_dir . '/' . $this->chunk_part_name($params['key'], $i);
|
||||
if (!is_file($part)) {
|
||||
$missing++;
|
||||
break;
|
||||
}
|
||||
fwrite($fp, (string) file_get_contents($part));
|
||||
}
|
||||
fclose($fp);
|
||||
|
||||
if ($missing) {
|
||||
@unlink($merge_path);
|
||||
return new \WP_Error('aksrt_chunk_missing', __('文件缺少分片,正在重新上传缺失分片', 'aksrt'), ['status' => 409]);
|
||||
}
|
||||
|
||||
$merged_size = (int) @filesize($merge_path);
|
||||
if ($merged_size <= 0 || ($params['size'] > 0 && $merged_size !== $params['size'])) {
|
||||
@unlink($merge_path);
|
||||
return new \WP_Error('aksrt_chunk_size_mismatch', __('合并后的文件大小校验失败,请重新上传', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if ($merged_size > $max_size) {
|
||||
@unlink($merge_path);
|
||||
return new \WP_Error('aksrt_upload_too_large', sprintf(__('文件大小不能超过 %s。', 'aksrt'), size_format($max_size)), ['status' => 413]);
|
||||
}
|
||||
|
||||
$name = sanitize_file_name($params['name']);
|
||||
$type = (string) (wp_check_filetype_and_ext($merge_path, $name, get_allowed_mime_types())['type'] ?? '');
|
||||
|
||||
$result = $this->persist_uploaded_file($params['kind'], [
|
||||
'tmp_name' => $merge_path,
|
||||
'name' => $name,
|
||||
'size' => $merged_size,
|
||||
'type' => $type,
|
||||
'error' => UPLOAD_ERR_OK,
|
||||
], true);
|
||||
|
||||
// 无论落库成败都清理本次分片(persist 成功时已 copy 走合并文件,失败时也要清场)。
|
||||
foreach ($this->chunk_part_paths($temp_dir, $params['key']) as $path) {
|
||||
@unlink($path);
|
||||
}
|
||||
@unlink($merge_path);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/** 校验并归一分片参数(name/chunks/chunk/size/key)。 */
|
||||
private function chunk_params(\WP_REST_Request $request, bool $with_index = false) {
|
||||
$name = sanitize_file_name((string) $request->get_param('name'));
|
||||
$chunks = (int) $request->get_param('chunks');
|
||||
$chunk = (int) $request->get_param('chunk');
|
||||
|
||||
if ($name === '' || $chunks < 1 || $chunks > 20480) {
|
||||
return new \WP_Error('aksrt_chunk_invalid_params', __('分片参数异常', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
if ($with_index && ($chunk < 0 || $chunk >= $chunks)) {
|
||||
return new \WP_Error('aksrt_chunk_invalid_params', __('分片参数异常', 'aksrt'), ['status' => 400]);
|
||||
}
|
||||
|
||||
return [
|
||||
'kind' => (string) $request->get_param('kind'),
|
||||
'name' => $name,
|
||||
'chunks' => $chunks,
|
||||
'chunk' => $chunk,
|
||||
'size' => (int) $request->get_param('size'),
|
||||
// 分片按用户隔离,避免同名文件互相覆盖、也防止探测他人分片。
|
||||
'key' => md5(get_current_blog_id() . '|' . get_current_user_id() . '|' . $name),
|
||||
];
|
||||
}
|
||||
|
||||
/** 分片临时目录(参考主题为 wp-content/zib-temp,带 index.php 保护和 48h 清理)。 */
|
||||
private function chunk_temp_dir(): string {
|
||||
$temp_dir = (string) apply_filters('aksrt_temp_dir', WP_CONTENT_DIR . '/aksrt-temp');
|
||||
if (!@is_dir($temp_dir)) {
|
||||
wp_mkdir_p($temp_dir);
|
||||
}
|
||||
$index_file = $temp_dir . '/index.php';
|
||||
if (!file_exists($index_file)) {
|
||||
@file_put_contents($index_file, "<?php\n// Silence is golden.\n");
|
||||
}
|
||||
return $temp_dir;
|
||||
}
|
||||
|
||||
/** 清理超过 48 小时的遗留分片。 */
|
||||
private function chunk_temp_cleanup(string $temp_dir): void {
|
||||
$expire = time() - 48 * HOUR_IN_SECONDS;
|
||||
foreach ((array) glob($temp_dir . '/*.part') as $file) {
|
||||
if (@filemtime((string) $file) < $expire) {
|
||||
@unlink((string) $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 当前 key 的全部分片路径(不含 merge 产物)。 */
|
||||
private function chunk_part_paths(string $temp_dir, string $key): array {
|
||||
$paths = [];
|
||||
foreach ((array) glob($temp_dir . '/' . $this->chunk_part_name($key, '*')) as $path) {
|
||||
$path = (string) $path;
|
||||
if (substr($path, -10) === 'merge.part') {
|
||||
continue;
|
||||
}
|
||||
$paths[] = $path;
|
||||
}
|
||||
sort($paths, SORT_NATURAL);
|
||||
return $paths;
|
||||
}
|
||||
|
||||
private function chunk_part_name(string $key, $index): string {
|
||||
return sprintf('%d-%s-%s.part', get_current_blog_id(), $key, (string) $index);
|
||||
}
|
||||
|
||||
private function chunk_index_from_name(string $path): int {
|
||||
$base = basename($path, '.part');
|
||||
$dash = strrpos($base, '-');
|
||||
return $dash === false ? -1 : (int) substr($base, $dash + 1);
|
||||
}
|
||||
}
|
||||
@@ -213,8 +213,6 @@ trait ScriptsPart1c {
|
||||
'upload_ext' => implode('|', array_keys(get_allowed_mime_types())),
|
||||
'user_upload_nonce' => wp_create_nonce('user_upload'),
|
||||
'post_action_nonce' => wp_create_nonce('post_action'),
|
||||
'is_split_upload' => false,
|
||||
'split_minimum_size' => 10,
|
||||
'translate_config' => [],
|
||||
'i18n' => [
|
||||
'load_more' => __('加载更多', 'aksrt'),
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
/**
|
||||
* 系统工具:WordPress 优化、上传优化与编辑器显示开关。
|
||||
*
|
||||
* 对应后台「扩展&增强 → 系统工具」设置(ExtensionRuntime::get_system_tools_settings()),
|
||||
* 行为对齐参考主题 zib-theme.php / zib-head.php / admin-main.php / admin-set.php 的系统工具套件。
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Extensions;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class SystemTools {
|
||||
|
||||
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 {
|
||||
$settings = \AKSRT\Settings\ExtensionRuntime::get_system_tools_settings();
|
||||
|
||||
if (!empty($settings['hide_admin_bar']) || !is_super_admin()) {
|
||||
add_filter('show_admin_bar', '__return_false');
|
||||
}
|
||||
|
||||
if (!empty($settings['remove_emoji'])) {
|
||||
$this->remove_emoji();
|
||||
}
|
||||
|
||||
if (!empty($settings['remove_open_sans'])) {
|
||||
add_action('init', [$this, 'remove_open_sans']);
|
||||
}
|
||||
|
||||
if (!empty($settings['remove_more_wp_head'])) {
|
||||
add_action('after_setup_theme', [$this, 'remove_more_wp_head']);
|
||||
}
|
||||
|
||||
if (!empty($settings['remove_dashboard_widgets'])) {
|
||||
add_action('do_meta_boxes', [$this, 'remove_dashboard_widgets'], 10, 3);
|
||||
add_filter('site_status_tests', [$this, 'filter_site_status_tests']);
|
||||
add_filter('pre_http_request', [$this, 'block_site_health_requests'], 10, 3);
|
||||
}
|
||||
|
||||
if (!empty($settings['display_wp_update'])) {
|
||||
$this->disable_wp_update();
|
||||
}
|
||||
|
||||
if (!empty($settings['disabled_large_thumbnail'])) {
|
||||
add_action('after_setup_theme', [$this, 'disable_large_thumbnail']);
|
||||
}
|
||||
|
||||
if (!empty($settings['close_gutenberg'])) {
|
||||
add_filter('use_block_editor_for_post', '__return_false');
|
||||
}
|
||||
|
||||
if (!empty($settings['disabled_autoembed'])) {
|
||||
$this->disable_autoembed();
|
||||
}
|
||||
|
||||
if (!empty($settings['admin_user_del_fields'])) {
|
||||
add_action('show_user_profile', [$this, 'hide_user_profile_fields']);
|
||||
add_action('edit_user_profile', [$this, 'hide_user_profile_fields']);
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除WordPress自带Emoji表情(对齐参考主题 remove_emoji 套件)。 */
|
||||
private function remove_emoji(): void {
|
||||
remove_action('admin_print_scripts', 'print_emoji_detection_script');
|
||||
remove_action('admin_print_styles', 'print_emoji_styles');
|
||||
remove_action('wp_head', 'print_emoji_detection_script', 7);
|
||||
remove_action('wp_print_styles', 'print_emoji_styles');
|
||||
remove_filter('the_content_feed', 'wp_staticize_emoji');
|
||||
remove_filter('comment_text_rss', 'wp_staticize_emoji');
|
||||
remove_filter('wp_mail', 'wp_staticize_emoji_for_email');
|
||||
}
|
||||
|
||||
/** 禁用Google字体(Open Sans)。 */
|
||||
public function remove_open_sans(): void {
|
||||
wp_deregister_style('open-sans');
|
||||
wp_register_style('open-sans', false);
|
||||
wp_enqueue_style('open-sans', '');
|
||||
}
|
||||
|
||||
/** 清理wp_head带入的多余Meta标签。 */
|
||||
public function remove_more_wp_head(): void {
|
||||
remove_action('wp_head', 'feed_links_extra', 3);
|
||||
remove_action('wp_head', 'feed_links', 2);
|
||||
remove_action('wp_head', 'rsd_link');
|
||||
remove_action('wp_head', 'wlwmanifest_link');
|
||||
remove_action('wp_head', 'index_rel_link');
|
||||
remove_action('wp_head', 'parent_post_rel_link', 10, 0);
|
||||
remove_action('wp_head', 'start_post_rel_link', 10, 0);
|
||||
remove_action('wp_head', 'adjacent_posts_rel_link', 10, 0);
|
||||
remove_action('wp_head', 'wp_generator');
|
||||
remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0);
|
||||
remove_action('wp_head', 'wp_shortlink_wp_head', 10, 0);
|
||||
remove_action('wp_head', 'rest_output_link_wp_head', 10, 0);
|
||||
remove_action('wp_head', 'wp_oembed_add_discovery_links', 10, 1);
|
||||
remove_action('wp_head', 'rel_canonical', 10, 0);
|
||||
}
|
||||
|
||||
/** 移除仪表盘的站点健康、PHP/浏览器版本检查小组件。 */
|
||||
public function remove_dashboard_widgets($screen_id, $context): void {
|
||||
if ('dashboard' !== $screen_id || 'normal' !== $context) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wp_meta_boxes;
|
||||
|
||||
unset($wp_meta_boxes['dashboard']['normal']['high']['dashboard_php_nag']);
|
||||
unset($wp_meta_boxes['dashboard']['normal']['core']['dashboard_site_health']);
|
||||
unset($wp_meta_boxes['dashboard']['side']['core']['dashboard_primary']);
|
||||
}
|
||||
|
||||
/** 站点健康检查中移除页面缓存与PHP版本检测(非常耗时)。 */
|
||||
public function filter_site_status_tests(array $tests): array {
|
||||
if (isset($tests['async']['page_cache'])) {
|
||||
unset($tests['async']['page_cache']);
|
||||
}
|
||||
if (isset($tests['direct']['php_version'])) {
|
||||
unset($tests['direct']['php_version']);
|
||||
}
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/** 禁止后台联网检测PHP版本、浏览器与贡献者信息,提高后台加载速度。 */
|
||||
public function block_site_health_requests($pre, $parsed_args, $url) {
|
||||
$blocked = [
|
||||
'api.wordpress.org/core/serve-happy/',
|
||||
'api.wordpress.org/core/browse-happy/',
|
||||
'api.wordpress.org/core/credits/',
|
||||
];
|
||||
|
||||
foreach ($blocked as $arg) {
|
||||
if (strpos((string) $url, $arg) !== false) {
|
||||
return [
|
||||
'headers' => [],
|
||||
'body' => '',
|
||||
'response' => [
|
||||
'code' => false,
|
||||
'message' => false,
|
||||
],
|
||||
'cookies' => [],
|
||||
'http_response' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $pre;
|
||||
}
|
||||
|
||||
/** 禁止WordPress检测核心/插件/主题更新,并屏蔽更新通知。 */
|
||||
private function disable_wp_update(): void {
|
||||
remove_action('admin_init', '_maybe_update_core');
|
||||
remove_action('admin_init', '_maybe_update_plugins');
|
||||
remove_action('admin_init', '_maybe_update_themes');
|
||||
add_action('admin_menu', [$this, 'hide_update_notices']);
|
||||
|
||||
add_filter('automatic_updater_disabled', '__return_true');
|
||||
remove_action('init', 'wp_schedule_update_checks');
|
||||
remove_action('wp_version_check', 'wp_version_check');
|
||||
remove_action('wp_update_plugins', 'wp_update_plugins');
|
||||
remove_action('wp_update_themes', 'wp_update_themes');
|
||||
add_filter('auto_update_core', '__return_false');
|
||||
add_filter('allow_major_auto_core_updates', '__return_false');
|
||||
add_filter('allow_dev_auto_core_updates', '__return_false');
|
||||
add_filter('allow_minor_auto_core_updates', '__return_false');
|
||||
}
|
||||
|
||||
/** 禁用更新通知。 */
|
||||
public function hide_update_notices(): void {
|
||||
remove_action('admin_notices', 'update_nag', 3);
|
||||
remove_action('network_admin_notices', 'update_nag', 3);
|
||||
}
|
||||
|
||||
/** 禁止WP大尺寸缩略图裁剪(medium_large、1536x1536、2048x2048)。 */
|
||||
public function disable_large_thumbnail(): void {
|
||||
remove_image_size('1536x1536');
|
||||
remove_image_size('2048x2048');
|
||||
add_filter('intermediate_image_sizes', static function ($sizes) {
|
||||
return array_merge(array_diff((array) $sizes, ['medium_large', '1536x1536', '2048x2048']));
|
||||
}, 20);
|
||||
}
|
||||
|
||||
/** 禁用Auto Embed自动嵌入功能,提高页面渲染速度。 */
|
||||
private function disable_autoembed(): void {
|
||||
remove_filter('the_content', [$GLOBALS['wp_embed'], 'autoembed'], 8);
|
||||
remove_filter('widget_text_content', [$GLOBALS['wp_embed'], 'autoembed'], 8);
|
||||
remove_filter('widget_block_content', [$GLOBALS['wp_embed'], 'autoembed'], 8);
|
||||
remove_action('edit_form_advanced', [$GLOBALS['wp_embed'], 'maybe_run_ajax_cache']);
|
||||
remove_action('edit_page_form', [$GLOBALS['wp_embed'], 'maybe_run_ajax_cache']);
|
||||
}
|
||||
|
||||
/** 关闭后台用户编辑页面中的多余选项。 */
|
||||
public function hide_user_profile_fields(): void {
|
||||
echo '<style>.user-first-name-wrap,.user-last-name-wrap,.user-admin-bar-front-wrap,.user-comment-shortcuts-wrap,.user-syntax-highlighting-wrap,.user-rich-editing-wrap,.user-profile-picture .description,.user-language-wrap{display:none}</style>';
|
||||
}
|
||||
}
|
||||
@@ -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>';
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,55 @@ class ExtensionRuntime {
|
||||
];
|
||||
}
|
||||
|
||||
/** 获取系统工具设置(WordPress 优化、上传优化、编辑器与显示)。 */
|
||||
public static function get_system_tools_settings(): array {
|
||||
$settings = Settings::get_all();
|
||||
|
||||
$switch = static function (string $key, bool $default) use ($settings): bool {
|
||||
return !isset($settings[$key]) ? $default : !empty($settings[$key]);
|
||||
};
|
||||
|
||||
$rename_type = sanitize_key((string) ($settings['newfilename_type'] ?? 'time'));
|
||||
if (!in_array($rename_type, ['random', 'time'], true)) {
|
||||
$rename_type = 'time';
|
||||
}
|
||||
|
||||
// Only fill defaults when the key was never saved — empty array means "none".
|
||||
$toolbar_scenes = ['pc_s', 'm_s'];
|
||||
if (!array_key_exists('mce_float_toolbar', $settings)) {
|
||||
$float_toolbar = $toolbar_scenes;
|
||||
} else {
|
||||
$float_toolbar = [];
|
||||
foreach ((array) ($settings['mce_float_toolbar'] ?? []) as $scene) {
|
||||
$scene = sanitize_key((string) $scene);
|
||||
if (in_array($scene, $toolbar_scenes, true) && !in_array($scene, $float_toolbar, true)) {
|
||||
$float_toolbar[] = $scene;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'hide_admin_bar' => $switch('hide_admin_bar', true),
|
||||
'remove_emoji' => $switch('remove_emoji', true),
|
||||
'remove_open_sans' => $switch('remove_open_sans', true),
|
||||
'remove_more_wp_head' => $switch('remove_more_wp_head', true),
|
||||
'remove_dashboard_widgets' => $switch('remove_dashboard_widgets', true),
|
||||
'display_wp_update' => $switch('display_wp_update', true),
|
||||
'split_upload_s' => $switch('split_upload_s', true),
|
||||
'split_minimum_size' => max(4, min(100, (int) ($settings['split_minimum_size'] ?? 20))),
|
||||
'newfilename' => $switch('newfilename', false),
|
||||
'newfilename_type' => $rename_type,
|
||||
'disabled_large_thumbnail' => $switch('disabled_large_thumbnail', true),
|
||||
'admin_user_del_fields' => $switch('admin_user_del_fields', true),
|
||||
'admin_frontend_set' => $switch('admin_frontend_set', true),
|
||||
'close_gutenberg' => $switch('close_gutenberg', false),
|
||||
'disabled_autoembed' => $switch('disabled_autoembed', true),
|
||||
'mce_float_toolbar' => $float_toolbar,
|
||||
'time_ago_s' => $switch('time_ago_s', true),
|
||||
'time_format' => (string) ($settings['time_format'] ?? 'n月j日 H:i'),
|
||||
];
|
||||
}
|
||||
|
||||
/** 获取短信接口设置。 */
|
||||
public static function get_sms_settings(): array {
|
||||
$settings = Settings::get_all();
|
||||
|
||||
Reference in New Issue
Block a user