PluginProbe
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent / 3.5.4
SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent v3.5.4
3.5.4 3.5.3 3.5.2 3.5.1 3.4.9 3.5.0 3.4.8 3.4.7 trunk 2.3.1 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6
supportcandy / includes / admin / ai-chatbot / tools / core / class-wpsc-acb-get-ticket-status.php

class-wpsc-acb-get-ticket-status.php in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent 3.5.4, at includes/admin/ai-chatbot/tools/core/class-wpsc-acb-get-ticket-status.php

289 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 exit; // Exit if accessed directly!
4 }
5
6 if ( ! class_exists( 'WPSC_ACB_Get_Ticket_Status' ) ) :
7
8 final class WPSC_ACB_Get_Ticket_Status {
9
10 /**
11 * Initialize the tool.
12 */
13 public static function init() {
14
15 add_filter( 'wpsc_acb_tool_registry', array( __CLASS__, 'register_tool' ) );
16 }
17
18 /**
19 * Register tool definition.
20 *
21 * @param array $registry Current registry.
22 * @return array
23 */
24 public static function register_tool( $registry ) {
25
26 $registry['get_ticket_status'] = array(
27 'name' => 'get_ticket_status',
28 'description' => 'Get ticket status details securely. Always use this tool when customer asks for ticket status, ticket update, or ticket progress. Never guess or fabricate ticket_id or email values. For logged-in users, ask for ticket_id first if it is not already shared by the user, then return details. For guests, require both ticket_id and email; the tool checks that the email matches the ticket\'s own customer email and returns the ticket details directly when it matches.',
29 'parameters' => array(
30 'type' => 'object',
31 'properties' => array(
32 'ticket_id' => array(
33 'type' => 'string',
34 'description' => __( 'Ticket identifier shared by customer (for example: 123 or Ticket #123).', 'supportcandy' ),
35 ),
36 'email' => array(
37 'type' => 'string',
38 'description' => __( 'Customer email. Required for guest verification.', 'supportcandy' ),
39 ),
40 ),
41 'additionalProperties' => false,
42 ),
43 'handler' => 'execute_tool_get_ticket_status',
44 'class' => __CLASS__,
45 );
46
47 return $registry;
48 }
49
50 /**
51 * Execute get ticket status tool.
52 *
53 * Returns structured data only (a 'need' code describing what's missing,
54 * or a 'ticket' payload on success); the calling LLM turn composes the
55 * actual user-facing reply (in the user's own language) from this result.
56 *
57 * @param array $args Tool arguments.
58 * @param string $session_uuid Session UUID.
59 * @return array
60 */
61 public static function execute_tool_get_ticket_status( $args, $session_uuid ) {
62
63 $parsed_ticket_id = self::parse_ticket_id( $args['ticket_id'] ?? '' );
64 if ( ! $parsed_ticket_id || ! self::was_ticket_id_shared_by_user( $parsed_ticket_id ) ) {
65 return array(
66 'success' => true,
67 'need' => 'ticket_id',
68 );
69 }
70
71 $ticket = self::get_ticket_object( $parsed_ticket_id );
72 if ( ! $ticket ) {
73 return array(
74 'success' => true,
75 'need' => 'ticket_not_found',
76 );
77 }
78
79 $current_user = WPSC_Current_User::$current_user;
80 if ( self::is_logged_in_user() ) {
81
82 if ( ! self::can_logged_in_user_access_ticket( $ticket, $current_user ) ) {
83 return array(
84 'success' => true,
85 'need' => 'permission_denied',
86 );
87 }
88
89 return array(
90 'success' => true,
91 'ticket' => self::build_ticket_status_data( $ticket ),
92 );
93 }
94
95 $email_raw = trim( (string) ( $args['email'] ?? '' ) );
96 $email = sanitize_email( $email_raw );
97
98 if ( '' === $email_raw || ! is_email( $email_raw ) ) {
99 return array(
100 'success' => true,
101 'need' => 'guest_email',
102 );
103 }
104
105 if ( ! self::is_guest_authorized_for_ticket( $ticket, $email ) ) {
106 return array(
107 'success' => true,
108 'need' => 'guest_verification_failed',
109 );
110 }
111
112 // Guest's email matches the ticket's own customer email - that is the full
113 // verification for this tool now; no OTP round-trip.
114 return array(
115 'success' => true,
116 'ticket' => self::build_ticket_status_data( $ticket ),
117 );
118 }
119
120 /**
121 * Parse ticket id from customer input.
122 *
123 * @param mixed $ticket_id Raw ticket id input.
124 * @return int
125 */
126 private static function parse_ticket_id( $ticket_id ) {
127
128 $ticket_id = is_scalar( $ticket_id ) ? trim( (string) $ticket_id ) : '';
129 if ( '' === $ticket_id ) {
130 return 0;
131 }
132
133 if ( ctype_digit( $ticket_id ) ) {
134 return (int) $ticket_id;
135 }
136
137 if ( preg_match( '/(\d+)/', $ticket_id, $matches ) ) {
138 return (int) $matches[1];
139 }
140
141 return 0;
142 }
143
144 /**
145 * Check whether ticket id was actually shared by user in chat history.
146 *
147 * @param int $ticket_id Ticket id.
148 * @return bool
149 */
150 private static function was_ticket_id_shared_by_user( $ticket_id ) {
151
152 $ticket_id = absint( $ticket_id );
153 if ( ! $ticket_id ) {
154 return false;
155 }
156
157 if ( ! class_exists( 'WPSC_ACB_Chats' ) ) {
158 return false;
159 }
160
161 $history = WPSC_ACB_Chats::get_conversation_history();
162 if ( ! is_array( $history ) || empty( $history ) ) {
163 return false;
164 }
165
166 $ticket_id_string = (string) $ticket_id;
167 foreach ( $history as $message ) {
168
169 $role = isset( $message['role'] ) ? sanitize_key( (string) $message['role'] ) : '';
170 if ( 'user' !== $role ) {
171 continue;
172 }
173
174 $content = isset( $message['content'] ) ? wp_strip_all_tags( (string) $message['content'] ) : '';
175 if ( '' === $content ) {
176 continue;
177 }
178
179 $has_exact = preg_match( '/(^|\D)' . preg_quote( $ticket_id_string, '/' ) . '(\D|$)/', $content );
180 if ( 1 === $has_exact ) {
181 return true;
182 }
183 }
184
185 return false;
186 }
187
188 /**
189 * Get ticket object by id from active or archive tickets.
190 *
191 * @param int $ticket_id Ticket id.
192 * @return object|null
193 */
194 private static function get_ticket_object( $ticket_id ) {
195
196 $ticket_id = absint( $ticket_id );
197 if ( ! $ticket_id ) {
198 return null;
199 }
200
201 $ticket = new WPSC_Ticket( $ticket_id );
202 if ( $ticket->id ) {
203 return $ticket;
204 }
205
206 // currently we do not support archive tickets for this tool, but we can add it in future if needed.
207
208 return null;
209 }
210
211 /**
212 * Check if current visitor is logged in.
213 *
214 * @return bool
215 */
216 private static function is_logged_in_user() {
217
218 $current_user = WPSC_Current_User::$current_user;
219 if ( ! empty( $current_user->user->ID ) ) {
220 return true;
221 }
222
223 $user = wp_get_current_user();
224 return ! empty( $user->ID );
225 }
226
227 /**
228 * Check logged-in user permission for this ticket.
229 *
230 * @param WPSC_Ticket|WPSC_Archive_Ticket $ticket Ticket object.
231 * @param WPSC_Current_User $current_user Current user.
232 * @return bool
233 */
234 private static function can_logged_in_user_access_ticket( $ticket, $current_user ) {
235
236 if ( WPSC_Functions::is_site_admin() ) {
237 return true;
238 }
239
240 $customer_id = (int) $current_user->customer->id;
241 $ticket_customer_id = (int) $ticket->customer->id;
242
243 if ( $current_user->is_agent ) {
244 if ( WPSC_Agent::has_ticket_cap( $ticket, 'view' ) || ( $customer_id === $ticket_customer_id ) ) {
245 return true;
246 }
247 }
248
249 return $customer_id > 0 && $customer_id === $ticket_customer_id;
250 }
251
252 /**
253 * Verify guest can request this ticket.
254 *
255 * @param WPSC_Ticket|WPSC_Archive_Ticket $ticket Ticket object.
256 * @param string $email Guest email.
257 * @return bool
258 */
259 private static function is_guest_authorized_for_ticket( $ticket, $email ) {
260
261 $ticket_email = (string) $ticket->customer->email;
262 return '' !== $ticket_email && strtolower( $ticket_email ) === strtolower( (string) $email );
263 }
264
265 /**
266 * Build structured ticket status data for the calling LLM turn to
267 * compose a user-facing reply from.
268 *
269 * @param WPSC_Ticket|WPSC_Archive_Ticket $ticket Ticket object.
270 * @return array
271 */
272 private static function build_ticket_status_data( $ticket ) {
273
274 $last_updated = wp_date( 'M d, Y h:i A', ( $ticket->date_updated )->setTimezone( wp_timezone() )->getTimestamp() );
275
276 return array(
277 'ticket_id' => (int) $ticket->id,
278 'status' => (string) $ticket->status->name,
279 'priority' => (string) $ticket->priority->name,
280 'category' => (string) $ticket->category->name,
281 'last_updated' => (string) $last_updated,
282 'ticket_url' => (string) WPSC_Functions::get_ticket_url( $ticket->id, 1 ),
283 );
284 }
285 }
286
287 endif;
288 WPSC_ACB_Get_Ticket_Status::init();
289