feat(tickets): make ticket title/content/reply length limits configurable #40
No files matched your search
@@ -8,7 +8,7 @@ export const ticketSchema = {
|
|||||||
key: 'users.ticket',
|
key: 'users.ticket',
|
||||||
title: '工单中心',
|
title: '工单中心',
|
||||||
icon: 'fa fas fa-headset',
|
icon: 'fa fas fa-headset',
|
||||||
description: '工单功能开关、工单号前缀、提交公告与提交表单(图片上传、关联订单、催单冷却)配置;部门、状态与预设回复在本页其余分页中维护。',
|
description: '工单功能开关、工单号前缀、提交公告与提交表单(图片上传、关联订单、催单冷却、字数上限)配置;部门、状态与预设回复在本页其余分页中维护。',
|
||||||
defaults: {
|
defaults: {
|
||||||
ticket_s: true,
|
ticket_s: true,
|
||||||
ticket_num_prefix: 'T',
|
ticket_num_prefix: 'T',
|
||||||
@@ -18,6 +18,9 @@ export const ticketSchema = {
|
|||||||
ticket_attachment_max: 5,
|
ticket_attachment_max: 5,
|
||||||
ticket_order_link_s: true,
|
ticket_order_link_s: true,
|
||||||
ticket_urge_interval: 15,
|
ticket_urge_interval: 15,
|
||||||
|
ticket_title_max: 200,
|
||||||
|
ticket_content_max: 5000,
|
||||||
|
ticket_reply_max: 3000,
|
||||||
},
|
},
|
||||||
groups: [
|
groups: [
|
||||||
{
|
{
|
||||||
@@ -40,6 +43,9 @@ export const ticketSchema = {
|
|||||||
{ key: 'ticket_attachment_max', label: '单个工单图片上限', type: 'number', default: 5, min: 1, max: 9, help: '工单内容与每条回复各自可上传的图片数量上限' },
|
{ key: 'ticket_attachment_max', label: '单个工单图片上限', type: 'number', default: 5, min: 1, max: 9, help: '工单内容与每条回复各自可上传的图片数量上限' },
|
||||||
{ key: 'ticket_order_link_s', label: '允许关联订单', type: 'switch', default: true, help: '开启后新建工单时可选择关联本人一个已支付订单;关闭后工单与订单完全解耦' },
|
{ key: 'ticket_order_link_s', label: '允许关联订单', type: 'switch', default: true, help: '开启后新建工单时可选择关联本人一个已支付订单;关闭后工单与订单完全解耦' },
|
||||||
{ key: 'ticket_urge_interval', label: '催单冷却(分钟)', type: 'number', default: 15, min: 1, max: 1440, help: '同一工单两次催单之间的最小间隔' },
|
{ key: 'ticket_urge_interval', label: '催单冷却(分钟)', type: 'number', default: 15, min: 1, max: 1440, help: '同一工单两次催单之间的最小间隔' },
|
||||||
|
{ key: 'ticket_title_max', label: '标题字数上限', type: 'number', default: 200, min: 10, max: 200, help: '工单标题最多可输入的字数(前台提交与后台代客提交共用)' },
|
||||||
|
{ key: 'ticket_content_max', label: '问题描述字数上限', type: 'number', default: 5000, min: 100, max: 16000, help: '新建工单时问题描述最多可输入的字数(前台提交与后台代客提交共用)' },
|
||||||
|
{ key: 'ticket_reply_max', label: '回复字数上限', type: 'number', default: 3000, min: 100, max: 16000, help: '用户与客服每条回复(含后台内部备注)最多可输入的字数' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -35,6 +35,12 @@
|
|||||||
return parseInt(settings().attachmentMax, 10) || 5;
|
return parseInt(settings().attachmentMax, 10) || 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 字数上限(后台「工单中心 → 提交设置」配置,PHP 侧已夹取边界)。 */
|
||||||
|
function lengthMax(kind) {
|
||||||
|
var defaults = { title: 200, content: 5000, reply: 3000 };
|
||||||
|
return parseInt(settings()[kind + 'Max'], 10) || defaults[kind];
|
||||||
|
}
|
||||||
|
|
||||||
function escapeHtml(value) {
|
function escapeHtml(value) {
|
||||||
return String(value === null || value === undefined ? '' : value)
|
return String(value === null || value === undefined ? '' : value)
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
@@ -359,6 +365,7 @@
|
|||||||
settings: settings,
|
settings: settings,
|
||||||
attachmentEnabled: attachmentEnabled,
|
attachmentEnabled: attachmentEnabled,
|
||||||
attachmentMax: attachmentMax,
|
attachmentMax: attachmentMax,
|
||||||
|
lengthMax: lengthMax,
|
||||||
escapeHtml: escapeHtml,
|
escapeHtml: escapeHtml,
|
||||||
notyf: notyf,
|
notyf: notyf,
|
||||||
request: request,
|
request: request,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
var settings = core.settings;
|
var settings = core.settings;
|
||||||
var attachmentEnabled = core.attachmentEnabled;
|
var attachmentEnabled = core.attachmentEnabled;
|
||||||
var attachmentMax = core.attachmentMax;
|
var attachmentMax = core.attachmentMax;
|
||||||
|
var lengthMax = core.lengthMax;
|
||||||
var escapeHtml = core.escapeHtml;
|
var escapeHtml = core.escapeHtml;
|
||||||
var notyf = core.notyf;
|
var notyf = core.notyf;
|
||||||
var request = core.request;
|
var request = core.request;
|
||||||
@@ -448,11 +449,11 @@
|
|||||||
+ attachTrayHtml()
|
+ attachTrayHtml()
|
||||||
+ '<div class="aksrt-ticket-composer__input">'
|
+ '<div class="aksrt-ticket-composer__input">'
|
||||||
+ '<label class="screen-reader-text" for="' + fieldId + '">回复内容</label>'
|
+ '<label class="screen-reader-text" for="' + fieldId + '">回复内容</label>'
|
||||||
+ '<textarea id="' + fieldId + '" name="reply" rows="2" maxlength="3000"'
|
+ '<textarea id="' + fieldId + '" name="reply" rows="2" maxlength="' + lengthMax('reply') + '"'
|
||||||
+ ' placeholder="补充说明或回复客服…"></textarea></div>'
|
+ ' placeholder="补充说明或回复客服…"></textarea></div>'
|
||||||
+ '<div class="aksrt-ticket-composer__bar">'
|
+ '<div class="aksrt-ticket-composer__bar">'
|
||||||
+ '<span class="aksrt-ticket-composer__hint">Ctrl + Enter 发送</span>'
|
+ '<span class="aksrt-ticket-composer__hint">Ctrl + Enter 发送</span>'
|
||||||
+ '<span class="aksrt-ticket-counter" data-aksrt-ticket-counter="reply">0 / 3000</span>'
|
+ '<span class="aksrt-ticket-counter" data-aksrt-ticket-counter="reply">0 / ' + lengthMax('reply') + '</span>'
|
||||||
+ '<button type="submit" class="btn-primary" data-aksrt-ticket-submit>' + icon('fa-paper-plane') + '发送</button>'
|
+ '<button type="submit" class="btn-primary" data-aksrt-ticket-submit>' + icon('fa-paper-plane') + '发送</button>'
|
||||||
+ '</div></form>';
|
+ '</div></form>';
|
||||||
}
|
}
|
||||||
@@ -642,8 +643,8 @@
|
|||||||
|
|
||||||
html += field(
|
html += field(
|
||||||
'<label for="aksrt-ticket-f-title">工单标题<em aria-hidden="true">*</em></label>',
|
'<label for="aksrt-ticket-f-title">工单标题<em aria-hidden="true">*</em></label>',
|
||||||
'<input id="aksrt-ticket-f-title" type="text" name="title" maxlength="200" autocomplete="off" placeholder="一句话概括问题,如:支付成功但会员未开通">',
|
'<input id="aksrt-ticket-f-title" type="text" name="title" maxlength="' + lengthMax('title') + '" autocomplete="off" placeholder="一句话概括问题,如:支付成功但会员未开通">',
|
||||||
'<span class="aksrt-ticket-counter" data-aksrt-ticket-counter="title">0 / 200</span>'
|
'<span class="aksrt-ticket-counter" data-aksrt-ticket-counter="title">0 / ' + lengthMax('title') + '</span>'
|
||||||
);
|
);
|
||||||
|
|
||||||
html += '<fieldset class="aksrt-ticket-field aksrt-ticket-types" data-aksrt-ticket-types>'
|
html += '<fieldset class="aksrt-ticket-field aksrt-ticket-types" data-aksrt-ticket-types>'
|
||||||
@@ -667,8 +668,8 @@
|
|||||||
|
|
||||||
html += field(
|
html += field(
|
||||||
'<label for="aksrt-ticket-f-content">问题描述<em aria-hidden="true">*</em></label>',
|
'<label for="aksrt-ticket-f-content">问题描述<em aria-hidden="true">*</em></label>',
|
||||||
'<textarea id="aksrt-ticket-f-content" name="content" rows="5" maxlength="5000" placeholder="请描述问题现象、发生时间、操作步骤等,信息越完整处理越快"></textarea>',
|
'<textarea id="aksrt-ticket-f-content" name="content" rows="5" maxlength="' + lengthMax('content') + '" placeholder="请描述问题现象、发生时间、操作步骤等,信息越完整处理越快"></textarea>',
|
||||||
'<span class="aksrt-ticket-counter" data-aksrt-ticket-counter="content">0 / 5000</span>'
|
'<span class="aksrt-ticket-counter" data-aksrt-ticket-counter="content">0 / ' + lengthMax('content') + '</span>'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (attachmentEnabled()) {
|
if (attachmentEnabled()) {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ trait UsersMenusTicketsCenter {
|
|||||||
|
|
||||||
private const TICKET_PAGE_SLUG = 'aksrt_tickets';
|
private const TICKET_PAGE_SLUG = 'aksrt_tickets';
|
||||||
private const TICKET_PER_PAGE = 20;
|
private const TICKET_PER_PAGE = 20;
|
||||||
private const TICKET_CONTENT_MAX = 3000;
|
|
||||||
|
|
||||||
/** 待接单工单数(后台 admin_notices 用)。 */
|
/** 待接单工单数(后台 admin_notices 用)。 */
|
||||||
private function count_pending_tickets(): int {
|
private function count_pending_tickets(): int {
|
||||||
@@ -194,7 +193,7 @@ trait UsersMenusTicketsCenter {
|
|||||||
return 'reply_failed';
|
return 'reply_failed';
|
||||||
}
|
}
|
||||||
$content = trim($this->ticket_post_textarea('content'));
|
$content = trim($this->ticket_post_textarea('content'));
|
||||||
if ($content === '' || mb_strlen($content) > self::TICKET_CONTENT_MAX) {
|
if ($content === '' || mb_strlen($content) > \AKSRT\Ticket\TicketSettings::reply_max()) {
|
||||||
return 'reply_failed';
|
return 'reply_failed';
|
||||||
}
|
}
|
||||||
$reply_id = $repo->create_reply($id, 'admin', get_current_user_id(), $content, [], 0);
|
$reply_id = $repo->create_reply($id, 'admin', get_current_user_id(), $content, [], 0);
|
||||||
@@ -213,7 +212,7 @@ trait UsersMenusTicketsCenter {
|
|||||||
return 'failed';
|
return 'failed';
|
||||||
}
|
}
|
||||||
$content = trim($this->ticket_post_textarea('content'));
|
$content = trim($this->ticket_post_textarea('content'));
|
||||||
if ($content === '' || mb_strlen($content) > self::TICKET_CONTENT_MAX) {
|
if ($content === '' || mb_strlen($content) > \AKSRT\Ticket\TicketSettings::reply_max()) {
|
||||||
return 'failed';
|
return 'failed';
|
||||||
}
|
}
|
||||||
return \AKSRT\Ticket\TicketNoteService::create($id, $content, get_current_user_id()) > 0
|
return \AKSRT\Ticket\TicketNoteService::create($id, $content, get_current_user_id()) > 0
|
||||||
@@ -235,10 +234,10 @@ trait UsersMenusTicketsCenter {
|
|||||||
$content = trim($this->ticket_post_textarea('content'));
|
$content = trim($this->ticket_post_textarea('content'));
|
||||||
$type_id = isset($_POST['ticket_type_id']) ? absint($_POST['ticket_type_id']) : 0;
|
$type_id = isset($_POST['ticket_type_id']) ? absint($_POST['ticket_type_id']) : 0;
|
||||||
|
|
||||||
if ($user_id <= 0 || !get_userdata($user_id) || $title === '' || mb_strlen($title) > 200) {
|
if ($user_id <= 0 || !get_userdata($user_id) || $title === '' || mb_strlen($title) > \AKSRT\Ticket\TicketSettings::title_max()) {
|
||||||
return 'create_failed';
|
return 'create_failed';
|
||||||
}
|
}
|
||||||
if ($content === '' || mb_strlen($content) > self::TICKET_CONTENT_MAX) {
|
if ($content === '' || mb_strlen($content) > \AKSRT\Ticket\TicketSettings::content_max()) {
|
||||||
return 'create_failed';
|
return 'create_failed';
|
||||||
}
|
}
|
||||||
if (!\AKSRT\Ticket\TicketTypeService::exists($type_id)) {
|
if (!\AKSRT\Ticket\TicketTypeService::exists($type_id)) {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ trait UsersMenusTicketsDetail {
|
|||||||
$html .= $this->ticket_form_open('note', $id, 'aksrt-ticket-detail__note-form');
|
$html .= $this->ticket_form_open('note', $id, 'aksrt-ticket-detail__note-form');
|
||||||
$html .= wp_nonce_field('aksrt_ticket_note_' . $id, '_wpnonce', true, false);
|
$html .= wp_nonce_field('aksrt_ticket_note_' . $id, '_wpnonce', true, false);
|
||||||
$html .= '<label class="screen-reader-text" for="aksrt-ticket-note-' . $id . '">' . esc_html__('备注内容', 'aksrt') . '</label>';
|
$html .= '<label class="screen-reader-text" for="aksrt-ticket-note-' . $id . '">' . esc_html__('备注内容', 'aksrt') . '</label>';
|
||||||
$html .= '<textarea id="aksrt-ticket-note-' . $id . '" name="content" rows="2" required maxlength="' . (int) self::TICKET_CONTENT_MAX . '" placeholder="' . esc_attr__('记录处理进展、排查结论…', 'aksrt') . '"></textarea>';
|
$html .= '<textarea id="aksrt-ticket-note-' . $id . '" name="content" rows="2" required maxlength="' . (int) \AKSRT\Ticket\TicketSettings::reply_max() . '" placeholder="' . esc_attr__('记录处理进展、排查结论…', 'aksrt') . '"></textarea>';
|
||||||
$html .= '<button type="submit" class="button">' . esc_html__('添加备注', 'aksrt') . '</button>';
|
$html .= '<button type="submit" class="button">' . esc_html__('添加备注', 'aksrt') . '</button>';
|
||||||
$html .= '</form>';
|
$html .= '</form>';
|
||||||
$html .= '</section>';
|
$html .= '</section>';
|
||||||
@@ -123,7 +123,7 @@ trait UsersMenusTicketsDetail {
|
|||||||
$html = $this->ticket_form_open('reply', $id, 'aksrt-ticket-detail__reply-form');
|
$html = $this->ticket_form_open('reply', $id, 'aksrt-ticket-detail__reply-form');
|
||||||
$html .= wp_nonce_field('aksrt_ticket_reply_' . $id, '_wpnonce', true, false);
|
$html .= wp_nonce_field('aksrt_ticket_reply_' . $id, '_wpnonce', true, false);
|
||||||
$html .= '<label class="aksrt-ticket-detail__heading" for="' . esc_attr($field) . '">' . esc_html__('回复用户', 'aksrt') . '</label>';
|
$html .= '<label class="aksrt-ticket-detail__heading" for="' . esc_attr($field) . '">' . esc_html__('回复用户', 'aksrt') . '</label>';
|
||||||
$html .= '<textarea id="' . esc_attr($field) . '" name="content" rows="5" required maxlength="' . (int) self::TICKET_CONTENT_MAX . '"'
|
$html .= '<textarea id="' . esc_attr($field) . '" name="content" rows="5" required maxlength="' . (int) \AKSRT\Ticket\TicketSettings::reply_max() . '"'
|
||||||
. ' data-aksrt-ticket-reply-input placeholder="' . esc_attr__('填写回复内容,提交后用户将收到通知', 'aksrt') . '"></textarea>';
|
. ' data-aksrt-ticket-reply-input placeholder="' . esc_attr__('填写回复内容,提交后用户将收到通知', 'aksrt') . '"></textarea>';
|
||||||
$html .= '<div class="aksrt-ticket-detail__reply-foot">';
|
$html .= '<div class="aksrt-ticket-detail__reply-foot">';
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ trait UsersMenusTicketsDetail {
|
|||||||
}
|
}
|
||||||
$html .= '</select>';
|
$html .= '</select>';
|
||||||
}
|
}
|
||||||
$html .= '<span class="aksrt-ticket-detail__counter" data-aksrt-ticket-counter="' . esc_attr($field) . '">0 / ' . (int) self::TICKET_CONTENT_MAX . '</span>';
|
$html .= '<span class="aksrt-ticket-detail__counter" data-aksrt-ticket-counter="' . esc_attr($field) . '">0 / ' . (int) \AKSRT\Ticket\TicketSettings::reply_max() . '</span>';
|
||||||
$html .= '<button type="submit" class="button button-primary">' . esc_html__('提交回复', 'aksrt') . '</button>';
|
$html .= '<button type="submit" class="button button-primary">' . esc_html__('提交回复', 'aksrt') . '</button>';
|
||||||
$html .= '</div></form>';
|
$html .= '</div></form>';
|
||||||
return $html;
|
return $html;
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ trait UsersMenusTicketsList {
|
|||||||
}
|
}
|
||||||
echo '</select></label>';
|
echo '</select></label>';
|
||||||
printf(
|
printf(
|
||||||
'<label class="is-wide"><span>%s</span><input type="text" name="title" maxlength="200" required class="large-text" /></label>',
|
'<label class="is-wide"><span>%s</span><input type="text" name="title" maxlength="%d" required class="large-text" /></label>',
|
||||||
esc_html__('标题', 'aksrt')
|
esc_html__('标题', 'aksrt'),
|
||||||
|
\AKSRT\Ticket\TicketSettings::title_max()
|
||||||
);
|
);
|
||||||
if (\AKSRT\Ticket\TicketSettings::order_link_enabled()) {
|
if (\AKSRT\Ticket\TicketSettings::order_link_enabled()) {
|
||||||
printf(
|
printf(
|
||||||
@@ -58,7 +59,7 @@ trait UsersMenusTicketsList {
|
|||||||
printf(
|
printf(
|
||||||
'<label class="is-wide"><span>%s</span><textarea name="content" rows="4" maxlength="%d" required class="large-text"></textarea></label>',
|
'<label class="is-wide"><span>%s</span><textarea name="content" rows="4" maxlength="%d" required class="large-text"></textarea></label>',
|
||||||
esc_html__('问题描述', 'aksrt'),
|
esc_html__('问题描述', 'aksrt'),
|
||||||
(int) self::TICKET_CONTENT_MAX
|
\AKSRT\Ticket\TicketSettings::content_max()
|
||||||
);
|
);
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
printf(
|
printf(
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ class SchemaUsersTicket {
|
|||||||
['key' => 'ticket_attachment_max', 'label' => '单个工单图片上限', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 9, 'help' => '工单内容与每条回复各自可上传的图片数量上限'],
|
['key' => 'ticket_attachment_max', 'label' => '单个工单图片上限', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 9, 'help' => '工单内容与每条回复各自可上传的图片数量上限'],
|
||||||
['key' => 'ticket_order_link_s', 'label' => '允许关联订单', 'type' => 'switch', 'default' => true, 'help' => '开启后新建工单时可选择关联本人一个已支付订单;关闭后工单与订单完全解耦'],
|
['key' => 'ticket_order_link_s', 'label' => '允许关联订单', 'type' => 'switch', 'default' => true, 'help' => '开启后新建工单时可选择关联本人一个已支付订单;关闭后工单与订单完全解耦'],
|
||||||
['key' => 'ticket_urge_interval', 'label' => '催单冷却(分钟)', 'type' => 'number', 'default' => 15, 'min' => 1, 'max' => 1440, 'help' => '同一工单两次催单之间的最小间隔'],
|
['key' => 'ticket_urge_interval', 'label' => '催单冷却(分钟)', 'type' => 'number', 'default' => 15, 'min' => 1, 'max' => 1440, 'help' => '同一工单两次催单之间的最小间隔'],
|
||||||
|
['key' => 'ticket_title_max', 'label' => '标题字数上限', 'type' => 'number', 'default' => 200, 'min' => 10, 'max' => 200, 'help' => '工单标题最多可输入的字数(前台提交与后台代客提交共用)'],
|
||||||
|
['key' => 'ticket_content_max', 'label' => '问题描述字数上限', 'type' => 'number', 'default' => 5000, 'min' => 100, 'max' => 16000, 'help' => '新建工单时问题描述最多可输入的字数(前台提交与后台代客提交共用)'],
|
||||||
|
['key' => 'ticket_reply_max', 'label' => '回复字数上限', 'type' => 'number', 'default' => 3000, 'min' => 100, 'max' => 16000, 'help' => '用户与客服每条回复(含后台内部备注)最多可输入的字数'],
|
||||||
]],
|
]],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -498,10 +498,10 @@ trait ScriptsPart1c {
|
|||||||
'desc' => (string) ($report['desc'] ?? ''),
|
'desc' => (string) ($report['desc'] ?? ''),
|
||||||
];
|
];
|
||||||
})(),
|
})(),
|
||||||
// 工单配置(ticket-center.js):附件开关与上限、提交公告、催单冷却。
|
// 工单配置(ticket-center.js):附件开关与上限、提交公告、催单冷却、字数上限。
|
||||||
'ticket' => (function () {
|
'ticket' => (function () {
|
||||||
if (!class_exists('\AKSRT\Ticket\TicketSettings')) {
|
if (!class_exists('\AKSRT\Ticket\TicketSettings')) {
|
||||||
return ['enabled' => false, 'attachmentEnabled' => false, 'attachmentMax' => 5, 'notice' => ['open' => false, 'text' => ''], 'urgeInterval' => 15];
|
return ['enabled' => false, 'attachmentEnabled' => false, 'attachmentMax' => 5, 'notice' => ['open' => false, 'text' => ''], 'urgeInterval' => 15, 'titleMax' => 200, 'contentMax' => 5000, 'replyMax' => 3000];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
'enabled' => \AKSRT\Ticket\TicketSettings::enabled(),
|
'enabled' => \AKSRT\Ticket\TicketSettings::enabled(),
|
||||||
@@ -512,6 +512,9 @@ trait ScriptsPart1c {
|
|||||||
'text' => \AKSRT\Ticket\TicketSettings::notice_text(),
|
'text' => \AKSRT\Ticket\TicketSettings::notice_text(),
|
||||||
],
|
],
|
||||||
'urgeInterval' => \AKSRT\Ticket\TicketSettings::urge_interval(),
|
'urgeInterval' => \AKSRT\Ticket\TicketSettings::urge_interval(),
|
||||||
|
'titleMax' => \AKSRT\Ticket\TicketSettings::title_max(),
|
||||||
|
'contentMax' => \AKSRT\Ticket\TicketSettings::content_max(),
|
||||||
|
'replyMax' => \AKSRT\Ticket\TicketSettings::reply_max(),
|
||||||
];
|
];
|
||||||
})(),
|
})(),
|
||||||
'stickyEnabled' => class_exists('\AKSRT\Settings\Settings') ? !empty(\AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_s']) : true,
|
'stickyEnabled' => class_exists('\AKSRT\Settings\Settings') ? !empty(\AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_s']) : true,
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ trait TicketConfigRest {
|
|||||||
return new \WP_Error('aksrt_ticket_missing', __('工单不存在', 'aksrt'), ['status' => 404]);
|
return new \WP_Error('aksrt_ticket_missing', __('工单不存在', 'aksrt'), ['status' => 404]);
|
||||||
}
|
}
|
||||||
$content = trim((string) $request->get_param('content'));
|
$content = trim((string) $request->get_param('content'));
|
||||||
if ($content === '' || mb_strlen($content) > 2000) {
|
if ($content === '' || mb_strlen($content) > TicketSettings::reply_max()) {
|
||||||
return new \WP_Error('aksrt_ticket_note_content', __('请填写有效的备注内容', 'aksrt'), ['status' => 400]);
|
return new \WP_Error('aksrt_ticket_note_content', __('请填写有效的备注内容', 'aksrt'), ['status' => 400]);
|
||||||
}
|
}
|
||||||
if (TicketNoteService::create($id, $content, get_current_user_id()) <= 0) {
|
if (TicketNoteService::create($id, $content, get_current_user_id()) <= 0) {
|
||||||
@@ -265,7 +265,7 @@ trait TicketConfigRest {
|
|||||||
return new \WP_Error('aksrt_ticket_note_missing', __('备注不存在', 'aksrt'), ['status' => 404]);
|
return new \WP_Error('aksrt_ticket_note_missing', __('备注不存在', 'aksrt'), ['status' => 404]);
|
||||||
}
|
}
|
||||||
$content = trim((string) $request->get_param('content'));
|
$content = trim((string) $request->get_param('content'));
|
||||||
if ($content === '' || mb_strlen($content) > 2000) {
|
if ($content === '' || mb_strlen($content) > TicketSettings::reply_max()) {
|
||||||
return new \WP_Error('aksrt_ticket_note_content', __('请填写有效的备注内容', 'aksrt'), ['status' => 400]);
|
return new \WP_Error('aksrt_ticket_note_content', __('请填写有效的备注内容', 'aksrt'), ['status' => 400]);
|
||||||
}
|
}
|
||||||
if (!TicketNoteService::update($id, $content)) {
|
if (!TicketNoteService::update($id, $content)) {
|
||||||
|
|||||||
@@ -252,10 +252,10 @@ final class TicketRest {
|
|||||||
$type_id = absint($request->get_param('ticket_type_id'));
|
$type_id = absint($request->get_param('ticket_type_id'));
|
||||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||||
|
|
||||||
if ($title === '' || mb_strlen($title) > 200) {
|
if ($title === '' || mb_strlen($title) > TicketSettings::title_max()) {
|
||||||
return new \WP_Error('aksrt_ticket_title', __('请填写有效的工单标题', 'aksrt'), ['status' => 400]);
|
return new \WP_Error('aksrt_ticket_title', __('请填写有效的工单标题', 'aksrt'), ['status' => 400]);
|
||||||
}
|
}
|
||||||
if ($content === '' || mb_strlen($content) > 5000) {
|
if ($content === '' || mb_strlen($content) > TicketSettings::content_max()) {
|
||||||
return new \WP_Error('aksrt_ticket_content', __('请填写有效的问题描述', 'aksrt'), ['status' => 400]);
|
return new \WP_Error('aksrt_ticket_content', __('请填写有效的问题描述', 'aksrt'), ['status' => 400]);
|
||||||
}
|
}
|
||||||
if (!TicketTypeService::exists($type_id)) {
|
if (!TicketTypeService::exists($type_id)) {
|
||||||
@@ -308,7 +308,7 @@ final class TicketRest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$content = trim((string) $request->get_param('content'));
|
$content = trim((string) $request->get_param('content'));
|
||||||
if ($content === '' || mb_strlen($content) > 3000) {
|
if ($content === '' || mb_strlen($content) > TicketSettings::reply_max()) {
|
||||||
return new \WP_Error('aksrt_ticket_reply_content', __('请填写有效的回复内容', 'aksrt'), ['status' => 400]);
|
return new \WP_Error('aksrt_ticket_reply_content', __('请填写有效的回复内容', 'aksrt'), ['status' => 400]);
|
||||||
}
|
}
|
||||||
$quote_id = absint($request->get_param('quote_reply_id'));
|
$quote_id = absint($request->get_param('quote_reply_id'));
|
||||||
|
|||||||
@@ -311,10 +311,10 @@ final class TicketRestAdmin {
|
|||||||
if ($user_id <= 0 || !get_userdata($user_id)) {
|
if ($user_id <= 0 || !get_userdata($user_id)) {
|
||||||
return new \WP_Error('aksrt_ticket_user', __('请选择工单所属用户', 'aksrt'), ['status' => 400]);
|
return new \WP_Error('aksrt_ticket_user', __('请选择工单所属用户', 'aksrt'), ['status' => 400]);
|
||||||
}
|
}
|
||||||
if ($title === '' || mb_strlen($title) > 200) {
|
if ($title === '' || mb_strlen($title) > TicketSettings::title_max()) {
|
||||||
return new \WP_Error('aksrt_ticket_title', __('请填写有效的工单标题', 'aksrt'), ['status' => 400]);
|
return new \WP_Error('aksrt_ticket_title', __('请填写有效的工单标题', 'aksrt'), ['status' => 400]);
|
||||||
}
|
}
|
||||||
if ($content === '' || mb_strlen($content) > 5000) {
|
if ($content === '' || mb_strlen($content) > TicketSettings::content_max()) {
|
||||||
return new \WP_Error('aksrt_ticket_content', __('请填写有效的问题描述', 'aksrt'), ['status' => 400]);
|
return new \WP_Error('aksrt_ticket_content', __('请填写有效的问题描述', 'aksrt'), ['status' => 400]);
|
||||||
}
|
}
|
||||||
if (!TicketTypeService::exists($type_id)) {
|
if (!TicketTypeService::exists($type_id)) {
|
||||||
@@ -356,7 +356,7 @@ final class TicketRestAdmin {
|
|||||||
return new \WP_Error('aksrt_ticket_closed', __('工单已关闭,无法回复', 'aksrt'), ['status' => 409]);
|
return new \WP_Error('aksrt_ticket_closed', __('工单已关闭,无法回复', 'aksrt'), ['status' => 409]);
|
||||||
}
|
}
|
||||||
$content = trim((string) $request->get_param('content'));
|
$content = trim((string) $request->get_param('content'));
|
||||||
if ($content === '' || mb_strlen($content) > 3000) {
|
if ($content === '' || mb_strlen($content) > TicketSettings::reply_max()) {
|
||||||
return new \WP_Error('aksrt_ticket_reply_content', __('请填写有效的回复内容', 'aksrt'), ['status' => 400]);
|
return new \WP_Error('aksrt_ticket_reply_content', __('请填写有效的回复内容', 'aksrt'), ['status' => 400]);
|
||||||
}
|
}
|
||||||
$quote_id = absint($request->get_param('quote_reply_id'));
|
$quote_id = absint($request->get_param('quote_reply_id'));
|
||||||
|
|||||||
@@ -62,4 +62,19 @@ final class TicketSettings {
|
|||||||
public static function urge_interval(): int {
|
public static function urge_interval(): int {
|
||||||
return max(1, min(1440, (int) (self::all()['ticket_urge_interval'] ?? 15)));
|
return max(1, min(1440, (int) (self::all()['ticket_urge_interval'] ?? 15)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 工单标题字数上限(tickets.title 为 VARCHAR(200),故上界 200)。 */
|
||||||
|
public static function title_max(): int {
|
||||||
|
return max(10, min(200, (int) (self::all()['ticket_title_max'] ?? 200)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 工单问题描述字数上限(TEXT 列 65535 字节,utf8mb4 每字最多 4 字节,故上界 16000)。 */
|
||||||
|
public static function content_max(): int {
|
||||||
|
return max(100, min(16000, (int) (self::all()['ticket_content_max'] ?? 5000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 每条回复 / 后台内部备注字数上限。 */
|
||||||
|
public static function reply_max(): int {
|
||||||
|
return max(100, min(16000, (int) (self::all()['ticket_reply_max'] ?? 3000)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +26,9 @@ const FIELD_ORDER = [
|
|||||||
'ticket_attachment_max',
|
'ticket_attachment_max',
|
||||||
'ticket_order_link_s',
|
'ticket_order_link_s',
|
||||||
'ticket_urge_interval',
|
'ticket_urge_interval',
|
||||||
|
'ticket_title_max',
|
||||||
|
'ticket_content_max',
|
||||||
|
'ticket_reply_max',
|
||||||
];
|
];
|
||||||
|
|
||||||
const GROUPS = [
|
const GROUPS = [
|
||||||
@@ -128,6 +131,11 @@ test('TicketSettings falls back to schema defaults and clamps numerics', () => {
|
|||||||
assert.match(body, /max\(1, min\(9, \(int\)/);
|
assert.match(body, /max\(1, min\(9, \(int\)/);
|
||||||
assert.match(body, /max\(1, min\(1440, \(int\)/);
|
assert.match(body, /max\(1, min\(1440, \(int\)/);
|
||||||
|
|
||||||
|
// 字数上限:标题 10..200(VARCHAR(200)),描述 / 回复 100..16000(TEXT 65535 字节 / utf8mb4)。
|
||||||
|
assert.match(body, /max\(10, min\(200, \(int\) \(self::all\(\)\['ticket_title_max'\] \?\? 200\)\)\)/);
|
||||||
|
assert.match(body, /max\(100, min\(16000, \(int\) \(self::all\(\)\['ticket_content_max'\] \?\? 5000\)\)\)/);
|
||||||
|
assert.match(body, /max\(100, min\(16000, \(int\) \(self::all\(\)\['ticket_reply_max'\] \?\? 3000\)\)\)/);
|
||||||
|
|
||||||
// 公告文案走 kses(后台允许 HTML)。
|
// 公告文案走 kses(后台允许 HTML)。
|
||||||
assert.match(body, /wp_kses_post/);
|
assert.match(body, /wp_kses_post/);
|
||||||
// 分区名与 schema key 一致(常量在 all() 之前声明,故对整份文件断言)。
|
// 分区名与 schema key 一致(常量在 all() 之前声明,故对整份文件断言)。
|
||||||
|
|||||||
Reference in new issue
Block a user