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 / controller / class-wpsc-acb-cache.php

class-wpsc-acb-cache.php in SupportCandy – AI Customer Support Ticket System & Live Chatbot Agent 3.5.4, at includes/admin/ai-chatbot/controller/class-wpsc-acb-cache.php

240 lines 8.2 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_Cache' ) ) :
7
8 final class WPSC_ACB_Cache {
9
10 /**
11 * Initialize the cache.
12 */
13 public static function init() {}
14
15 /**
16 * Get the cache key for a given session ID and message.
17 *
18 * @param string $session_id The session ID.
19 * @return string The cache key.
20 */
21 public static function get_cache_label( $session_id ) {
22 return "wpsc_acb_cache_{$session_id}";
23 }
24
25 /**
26 * Retrieve a cached response for a given session ID and message.
27 *
28 * @param string $session_id The session ID.
29 * @return mixed The cached response or false if not found.
30 */
31 public static function get_acb_cache( $session_id ) {
32
33 $identity = self::get_logged_in_identity();
34 $default_data = array(
35 'transcript' => array(),
36 'user' => array(
37 'name' => $identity['name'],
38 'email' => $identity['email'],
39 ),
40 // Memoized "known user" system-prompt block for this session -
41 // see get_known_user_context()/set_known_user_context() below.
42 // null means "not computed yet this session"; '' is a valid
43 // cached result (e.g. an anonymous guest with nothing to say).
44 'known_user_context' => null,
45 );
46
47 // Ensure cache payload always has a consistent shape. array_merge()
48 // (rather than a straight overwrite) so a transient written by an
49 // older version of this class - before 'known_user_context' existed -
50 // still gets that key filled in instead of missing entirely.
51 $cache_label = self::get_cache_label( $session_id );
52 $cached_data = get_transient( $cache_label );
53 $cache_hit = is_array( $cached_data );
54
55 $cached_data = $cache_hit ? array_merge( $default_data, $cached_data ) : $default_data;
56
57 $previous_user = $cached_data['user'];
58
59 // Refresh identity in case a guest has since logged in during the conversation.
60 $cached_data['user'] = $default_data['user'];
61
62 // Only persist when something actually changed (fresh cache needing its
63 // defaults saved, or the identity refresh above found a real change) -
64 // this is called on every read (including from inside a per-message loop
65 // while rebuilding the transcript from the database), so writing the
66 // transient unconditionally here would turn every plain read into a write.
67 if ( ! $cache_hit || $previous_user !== $cached_data['user'] ) {
68 set_transient( $cache_label, $cached_data, HOUR_IN_SECONDS );
69 }
70
71 return $cached_data;
72 }
73
74 /**
75 * Set a cached response for a given session ID and message.
76 *
77 * @param string $session_id The session ID.
78 * @param string $sender The role of the message (user or assistant).
79 * @param string $message The message to cache.
80 */
81 public static function set_acb_chat_messages( $session_id, $sender, $message ) {
82
83 $session_id = (int) $session_id;
84 if ( $session_id <= 0 ) {
85 return;
86 }
87
88 $sender = is_string( $sender ) ? sanitize_key( $sender ) : '';
89 if ( ! in_array( $sender, array( 'user', 'assistant' ), true ) ) {
90 return;
91 }
92
93 $message = is_string( $message ) ? $message : '';
94 if ( '' === $message ) {
95 return;
96 }
97
98 $cache_label = self::get_cache_label( $session_id );
99 $cached_data = self::get_acb_cache( $session_id );
100
101 $cached_data['transcript'][] = array(
102 'role' => $sender,
103 'content' => $message,
104 'date_created' => ( new DateTime() )->format( 'Y-m-d H:i:s' ),
105 );
106 set_transient( $cache_label, $cached_data, HOUR_IN_SECONDS );
107 }
108
109 /**
110 * Replace the full cached transcript for a session in a single write.
111 *
112 * Used when rebuilding the transcript from the database after a cache
113 * miss - looping N messages through set_acb_chat_messages() there would
114 * cost 2 set_transient() writes per message (one from its internal
115 * get_acb_cache() call, one of its own) instead of the single write here.
116 *
117 * @param int $session_id Session ID.
118 * @param array $transcript Ordered list of ['role' => ..., 'content' => ..., 'date_created' => ...] entries.
119 * @return void
120 */
121 public static function set_acb_transcript( $session_id, array $transcript ) {
122
123 $session_id = (int) $session_id;
124 if ( $session_id <= 0 ) {
125 return;
126 }
127
128 $cache_label = self::get_cache_label( $session_id );
129 $cached_data = self::get_acb_cache( $session_id );
130 $cached_data['transcript'] = $transcript;
131 set_transient( $cache_label, $cached_data, HOUR_IN_SECONDS );
132 }
133
134 /**
135 * Get this session's memoized "known user" system-prompt block (see
136 * WPSC_ACB_Chats::get_known_user_context()), so it only has to be built
137 * once per chat session instead of on every single message - relevant
138 * once that block grows beyond a cheap in-memory identity read to
139 * include lookups elsewhere (e.g. WooCommerce order history, LMS
140 * course/progress data) that would otherwise re-run on every turn.
141 *
142 * Lives inside the same per-session transient as the transcript cache,
143 * so it is automatically destroyed together with it - whenever
144 * clear_acb_cache() runs (conversation resolved/skipped/escalation
145 * cancelled) or after an hour of inactivity, whichever comes first -
146 * rather than needing its own separate lifecycle to manage.
147 *
148 * @param int $session_id Session ID.
149 * @return string|null The cached block, or null if nothing has been cached yet this session.
150 */
151 public static function get_known_user_context( $session_id ) {
152
153 $cached_data = self::get_acb_cache( $session_id );
154 $context = is_string( $cached_data['known_user_context'] ?? null ) ? $cached_data['known_user_context'] : null;
155
156 return $context;
157 }
158
159 /**
160 * Store this session's "known user" system-prompt block for reuse by
161 * later messages in the same session. See get_known_user_context().
162 *
163 * @param int $session_id Session ID.
164 * @param string $context The rendered system-prompt block (may be '' for an anonymous guest).
165 * @return void
166 */
167 public static function set_known_user_context( $session_id, $context ) {
168
169 $session_id = (int) $session_id;
170 if ( $session_id <= 0 ) {
171 return;
172 }
173
174 $cache_label = self::get_cache_label( $session_id );
175 $cached_data = self::get_acb_cache( $session_id );
176 $cached_data['known_user_context'] = is_string( $context ) ? $context : '';
177 set_transient( $cache_label, $cached_data, HOUR_IN_SECONDS );
178 }
179
180 /**
181 * Clear the cached response for a given session ID.
182 *
183 * @param string $session_id The session ID.
184 */
185 public static function clear_acb_cache( $session_id ) {
186
187 $cache_label = self::get_cache_label( $session_id );
188
189 delete_transient( $cache_label );
190 }
191
192 /**
193 * Resolve logged-in identity for chatbot ticket creation.
194 *
195 * Must use the same "known customer" condition as
196 * WPSC_ACB_Chats::get_known_user_context() (is_customer + customer
197 * record on WPSC_Current_User::$current_user) rather than a raw
198 * WP_User/wp_get_current_user() check, so this cached identity can
199 * never disagree with what the model was told about the visitor.
200 *
201 * @return array
202 */
203 private static function get_logged_in_identity() {
204
205 $unknown_identity = array(
206 'is_logged_in' => false,
207 'name' => 'unknown',
208 'email' => '[email protected]',
209 );
210
211 $current_user = WPSC_Current_User::$current_user;
212
213 // Note: WPSC_Customer exposes 'id' via a magic __get() with no
214 // __isset(), and empty( $current_user->customer->id ) always
215 // evaluates true regardless of the actual value in that case -
216 // verified directly (empty() on a magic-getter-only property
217 // short-circuits before ever calling __get()). Read the value
218 // out first via a plain property access and test that instead.
219 $customer_id = empty( $current_user ) || empty( $current_user->is_customer ) || empty( $current_user->customer ) ? '' : $current_user->customer->id;
220 if ( ! $customer_id ) {
221 return $unknown_identity;
222 }
223
224 $name = sanitize_text_field( (string) $current_user->customer->name );
225 $email = sanitize_email( (string) $current_user->customer->email );
226
227 if ( '' === $name || ! is_email( $email ) ) {
228 return $unknown_identity;
229 }
230
231 return array(
232 'is_logged_in' => true,
233 'name' => $name,
234 'email' => $email,
235 );
236 }
237 }
238 endif;
239 WPSC_ACB_Cache::init();
240