feat(tickets): make ticket title/content/reply length limits configurable

Add ticket_title_max / ticket_content_max / ticket_reply_max to the
users/ticket schema (PHP + SPA) with clamped TicketSettings readers.
All write paths (front REST, admin REST, note REST, native admin page)
and front-end maxlength/counters now read the configured limits.
Remove the hardcoded TICKET_CONTENT_MAX constant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
kate522andClaude Fable 5 committed 2026-10-27 23:06:28 +08:00
1 parent 6681b0c306
commit 63ab8b8fc9
13 files changed
+71 -28

No files matched your search

+4 -5
View File
@@ -23,7 +23,6 @@ trait UsersMenusTicketsCenter {
private const TICKET_PAGE_SLUG = 'aksrt_tickets';
private const TICKET_PER_PAGE = 20;
private const TICKET_CONTENT_MAX = 3000;
/** 待接单工单数(后台 admin_notices 用)。 */
private function count_pending_tickets(): int {
@@ -194,7 +193,7 @@ trait UsersMenusTicketsCenter {
return 'reply_failed';
}
$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';
}
$reply_id = $repo->create_reply($id, 'admin', get_current_user_id(), $content, [], 0);
@@ -213,7 +212,7 @@ trait UsersMenusTicketsCenter {
return 'failed';
}
$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 \AKSRT\Ticket\TicketNoteService::create($id, $content, get_current_user_id()) > 0
@@ -235,10 +234,10 @@ trait UsersMenusTicketsCenter {
$content = trim($this->ticket_post_textarea('content'));
$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';
}
if ($content === '' || mb_strlen($content) > self::TICKET_CONTENT_MAX) {
if ($content === '' || mb_strlen($content) > \AKSRT\Ticket\TicketSettings::content_max()) {
return 'create_failed';
}
if (!\AKSRT\Ticket\TicketTypeService::exists($type_id)) {
+3 -3
View File
@@ -108,7 +108,7 @@ trait UsersMenusTicketsDetail {
$html .= $this->ticket_form_open('note', $id, 'aksrt-ticket-detail__note-form');
$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 .= '<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 .= '</form>';
$html .= '</section>';
@@ -123,7 +123,7 @@ trait UsersMenusTicketsDetail {
$html = $this->ticket_form_open('reply', $id, 'aksrt-ticket-detail__reply-form');
$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 .= '<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>';
$html .= '<div class="aksrt-ticket-detail__reply-foot">';
@@ -142,7 +142,7 @@ trait UsersMenusTicketsDetail {
}
$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 .= '</div></form>';
return $html;
+4 -3
View File
@@ -45,8 +45,9 @@ trait UsersMenusTicketsList {
}
echo '</select></label>';
printf(
'<label class="is-wide"><span>%s</span><input type="text" name="title" maxlength="200" required class="large-text" /></label>',
esc_html__('标题', 'aksrt')
'<label class="is-wide"><span>%s</span><input type="text" name="title" maxlength="%d" required class="large-text" /></label>',
esc_html__('标题', 'aksrt'),
\AKSRT\Ticket\TicketSettings::title_max()
);
if (\AKSRT\Ticket\TicketSettings::order_link_enabled()) {
printf(
@@ -58,7 +59,7 @@ trait UsersMenusTicketsList {
printf(
'<label class="is-wide"><span>%s</span><textarea name="content" rows="4" maxlength="%d" required class="large-text"></textarea></label>',
esc_html__('问题描述', 'aksrt'),
(int) self::TICKET_CONTENT_MAX
\AKSRT\Ticket\TicketSettings::content_max()
);
echo '</div>';
printf(
@@ -33,6 +33,9 @@ class SchemaUsersTicket {
['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_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' => '用户与客服每条回复(含后台内部备注)最多可输入的字数'],
]],
],
];