PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.3.1
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.3.1
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / modules / chatbot.php
ai-engine / classes / modules Last commit date
chatbot.php 2 years ago discussions.php 2 years ago files.php 2 years ago security.php 2 years ago tasks.php 2 years ago utilities.php 2 years ago wand.php 2 years ago
chatbot.php
573 lines
1 <?php
2
3 // Params for the chatbot (front and server)
4
5 define( 'MWAI_CHATBOT_FRONT_PARAMS', [ 'id', 'customId', 'aiName', 'userName', 'guestName',
6 'textSend', 'textClear', 'imageUpload', 'fileSearch',
7 'textInputPlaceholder', 'textInputMaxLength', 'textCompliance', 'startSentence', 'localMemory',
8 'themeId', 'window', 'icon', 'iconText', 'iconAlt', 'iconPosition', 'fullscreen', 'copyButton'
9 ] );
10 define( 'MWAI_CHATBOT_SERVER_PARAMS', [ 'id', 'envId', 'scope', 'mode', 'contentAware', 'context',
11 'embeddingsEnvId', 'embeddingsIndex', 'embeddingsNamespace', 'assistantId', 'instructions',
12 'model', 'temperature', 'maxTokens', 'contextMaxLength', 'maxResults', 'apiKey', 'functions'
13 ] );
14
15 // Params for the discussions (front and server)
16
17 define( 'MWAI_DISCUSSIONS_FRONT_PARAMS', [ 'themeId', 'textNewChat' ] );
18 define( 'MWAI_DISCUSSIONS_SERVER_PARAMS', [ 'customId' ] );
19
20 class Meow_MWAI_Modules_Chatbot {
21 private $core = null;
22 private $namespace = 'mwai-ui/v1';
23 private $siteWideChatId = null;
24
25 public function __construct() {
26 global $mwai_core;
27 $this->core = $mwai_core;
28 add_shortcode( 'mwai_chatbot', array( $this, 'chat_shortcode' ) );
29 add_shortcode( 'mwai_chatbot_v2', array( $this, 'chat_shortcode' ) );
30 add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
31 $this->siteWideChatId = $this->core->get_option( 'botId' );
32 add_action( 'wp_enqueue_scripts', array( $this, 'register_scripts' ) );
33
34 if ( $this->core->get_option( 'shortcode_chat_discussions' ) ) {
35 add_shortcode( 'mwai_discussions', [ $this, 'shortcode_chat_discussions' ] );
36 }
37 }
38
39 public function register_scripts() {
40 $physical_file = trailingslashit( MWAI_PATH ) . 'app/chatbot.js';
41 $cache_buster = file_exists( $physical_file ) ? filemtime( $physical_file ) : MWAI_VERSION;
42 wp_register_script( 'mwai_chatbot', trailingslashit( MWAI_URL ) . 'app/chatbot.js',
43 [ 'wp-element' ], $cache_buster, false );
44 if ( !empty( $this->siteWideChatId ) && $this->siteWideChatId !== 'none' ) {
45 $this->enqueue_scripts();
46 add_action( 'wp_footer', array( $this, 'inject_chat' ) );
47 }
48 }
49
50 public function enqueue_scripts() {
51 wp_enqueue_script( "mwai_chatbot" );
52 if ( $this->core->get_option( 'shortcode_chat_syntax_highlighting' ) ) {
53 wp_enqueue_script( "mwai_highlight" );
54 }
55 }
56
57 public function rest_api_init() {
58 register_rest_route( $this->namespace, '/chats/submit', array(
59 'methods' => 'POST',
60 'callback' => [ $this, 'rest_chat' ],
61 'permission_callback' => array( $this->core, 'check_rest_nonce' )
62 ) );
63 }
64
65 public function basics_security_check( $botId, $customId, $newMessage ) {
66 if ( empty( $newMessage ) ) {
67 error_log("AI Engine: The query was rejected - message was empty.");
68 return false;
69 }
70 if ( !$botId && !$customId ) {
71 error_log("AI Engine: The query was rejected - no botId nor id was specified.");
72 return false;
73 }
74
75 $length = strlen( $newMessage );
76 if ( $length < 1 || $length > ( 4096 * 16 ) ) {
77 error_log("AI Engine: The query was rejected - message was too short or too long.");
78 return false;
79 }
80 return true;
81 }
82
83 public function rest_chat( $request ) {
84 $params = $request->get_json_params();
85 $botId = $params['botId'] ?? null;
86 $customId = $params['customId'] ?? null;
87 $stream = $params['stream'] ?? false;
88 $newMessage = trim( $params['newMessage'] ?? '' );
89 $newFileId = $params['newFileId'] ?? null;
90
91 if ( !$this->basics_security_check( $botId, $customId, $newMessage )) {
92 return new WP_REST_Response( [
93 'success' => false,
94 'message' => apply_filters( 'mwai_ai_exception', 'Sorry, your query has been rejected.' )
95 ], 403 );
96 }
97
98 try {
99 $data = $this->chat_submit( $botId, $newMessage, $newFileId, $params, $stream );
100 return new WP_REST_Response( [
101 'success' => true,
102 'reply' => $data['reply'],
103 'images' => $data['images'],
104 'usage' => $data['usage']
105 ], 200 );
106 }
107 catch ( Exception $e ) {
108 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
109 return new WP_REST_Response( [
110 'success' => false,
111 'message' => $message
112 ], 500 );
113 }
114 }
115
116 public function chat_submit( $botId, $newMessage, $newFileId = null, $params = [], $stream = false ) {
117 try {
118 $chatbot = null;
119 $customId = $params['customId'] ?? null;
120
121 // Custom Chatbot
122 if ( $customId ) {
123 $chatbot = get_transient( 'mwai_custom_chatbot_' . $customId );
124 }
125 // Registered Chatbot
126 if ( !$chatbot && $botId ) {
127 $chatbot = $this->core->get_chatbot( $botId );
128 }
129
130 if ( !$chatbot ) {
131 error_log("AI Engine: No chatbot was found for this query.");
132 throw new Exception( 'Sorry, your query has been rejected.' );
133 }
134
135 $textInputMaxLength = $chatbot['textInputMaxLength'] ?? null;
136 if ( $textInputMaxLength && strlen( $newMessage ) > (int)$textInputMaxLength ) {
137 throw new Exception( 'Sorry, your query has been rejected.' );
138 }
139
140 // Create QueryText
141 $context = null;
142 $mode = $chatbot['mode'] ?? 'chat';
143
144 if ( $mode === 'images' ) {
145 $query = new Meow_MWAI_Query_Image( $newMessage );
146
147 // Handle Params
148 $newParams = [];
149 foreach ( $chatbot as $key => $value ) {
150 $newParams[$key] = $value;
151 }
152 foreach ( $params as $key => $value ) {
153 $newParams[$key] = $value;
154 }
155 $params = apply_filters( 'mwai_chatbot_params', $newParams );
156 $params['scope'] = empty( $params['scope'] ) ? 'chatbot' : $params['scope'];
157 $query->inject_params( $params );
158 }
159 else {
160 $query = $mode === 'assistant' ? new Meow_MWAI_Query_Assistant( $newMessage ) :
161 new Meow_MWAI_Query_Text( $newMessage, 1024 );
162 $streamCallback = null;
163
164 // Handle Params
165 $newParams = [];
166 foreach ( $chatbot as $key => $value ) {
167 $newParams[$key] = $value;
168 }
169 foreach ( $params as $key => $value ) {
170 $newParams[$key] = $value;
171 }
172 $params = apply_filters( 'mwai_chatbot_params', $newParams );
173 $params['scope'] = empty( $params['scope'] ) ? 'chatbot' : $params['scope'];
174 $query->inject_params( $params );
175
176 $storeId = null;
177 if ( $mode === 'assistant' ) {
178 $chatId = $params['chatId'] ?? null;
179 if ( !empty( $chatId ) ) {
180 $discussion = $this->core->discussions->get_discussion( $query->botId, $chatId );
181 if ( isset( $discussion['storeId'] ) ) {
182 $storeId = $discussion['storeId'];
183 $query->setStoreId( $storeId );
184 }
185 }
186 }
187
188 // Support for Uploaded Image
189 if ( !empty( $newFileId ) ) {
190
191 // Get extension and mime type
192 $isImage = $this->core->files->is_image( $newFileId );
193
194 if ( $mode === 'assistant' && !$isImage ) {
195 $url = $this->core->files->get_path( $newFileId );
196 $data = $this->core->files->get_data( $newFileId );
197 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $query->envId );
198 $filename = basename( $url );
199
200 // Upload the file
201 $file = $openai->upload_file( $filename, $data, 'assistants' );
202
203 // Create a store
204 if ( empty( $storeId ) ) {
205 $chatbotName = 'mwai_' . strtolower( !empty( $chatbot['name'] ) ? $chatbot['name'] : 'default' );
206 if ( !empty( $query->chatId ) ) {
207 $chatbotName .= "_" . $query->chatId;
208 }
209 $expiry = $this->core->get_option( 'image_expires' );
210 $metadata = [];
211 if ( !empty( $chatbot['assistantId'] ) ) {
212 $metadata['assistantId'] = $chatbot['assistantId'];
213 }
214 if ( !empty( $query->chatId ) ) {
215 $metadata['chatId'] = $query->chatId;
216 }
217 $storeId = $openai->create_vector_store( $chatbotName, $expiry, $metadata );
218 $query->setStoreId( $storeId );
219 }
220
221 // Add the file to the store
222 $storeFileId = $openai->add_vector_store_file( $storeId, $file['id'] );
223
224 // Update the local file with the OpenAI RefId, StoreId and StoreFileId
225 $openAiRefId = $file['id'];
226 $internalFileId = $this->core->files->get_id_from_refId( $newFileId );
227 $this->core->files->update_refId( $internalFileId, $openAiRefId );
228 $this->core->files->update_envId( $internalFileId, $query->envId );
229 $this->core->files->add_metadata( $internalFileId, 'assistant_storeId', $storeId );
230 $this->core->files->add_metadata( $internalFileId, 'assistant_storeFileId', $storeFileId );
231 $newFileId = $openAiRefId;
232 $scope = $params['fileSearch'];
233 if ( $scope === 'discussion' || $scope === 'user' || $scope === 'assistant' ) {
234 $id = $this->core->files->get_id_from_refId( $newFileId );
235 $this->core->files->add_metadata( $id, 'assistant_scope', $scope );
236 }
237 }
238 else {
239 $url = $this->core->files->get_url( $newFileId );
240 $mimeType = $this->core->files->get_mime_type( $newFileId );
241 $query->set_file( Meow_MWAI_Query_AttachedFile::from_url( $url, 'vision', $mimeType ) );
242
243 $fileId = $this->core->files->get_id_from_refId( $newFileId );
244 $this->core->files->update_envId( $fileId, $query->envId );
245 $this->core->files->add_metadata( $fileId, 'query_envId', $query->envId );
246 $this->core->files->add_metadata( $fileId, 'query_session', $query->session );
247 }
248 }
249
250 // Takeover
251 $takeoverAnswer = apply_filters( 'mwai_chatbot_takeover', null, $query, $params );
252 if ( !empty( $takeoverAnswer ) ) {
253 return [
254 'reply' => $takeoverAnswer,
255 'images' => null,
256 'usage' => null
257 ];
258 }
259
260 // Moderation
261 $moderationEnabled = $this->core->get_option( 'module_moderation' ) &&
262 $this->core->get_option( 'shortcode_chat_moderation' );
263 if ( $moderationEnabled ) {
264 global $mwai;
265 $isFlagged = $mwai->moderationCheck( $query->get_message() );
266 if ( $isFlagged ) {
267 throw new Exception( 'Sorry, your message has been rejected by moderation.' );
268 }
269 }
270
271 // Awareness & Embeddings
272 $context = $this->core->retrieve_context( $params, $query );
273 if ( !empty( $context ) ) {
274 $query->set_context( $context['content'] );
275 }
276
277 // Function Aware
278 $query = apply_filters( 'mwai_chatbot_query', $query, $params );
279 }
280
281 // Process Query
282 if ( $stream ) {
283 $streamCallback = function( $reply ) {
284 $raw = $reply;
285 $this->stream_push( [ 'type' => 'live', 'data' => $raw ] );
286 if ( ob_get_level() > 0 ) {
287 ob_flush();
288 }
289 flush();
290 };
291 header( 'Cache-Control: no-cache' );
292 header( 'Content-Type: text/event-stream' );
293 // This is useful to disable buffering in nginx through headers.
294 header( 'X-Accel-Buffering: no' );
295 ob_implicit_flush( true );
296 ob_end_flush();
297 }
298
299 $reply = $this->core->run_query( $query, $streamCallback, true );
300 $rawText = $reply->result;
301 $extra = [];
302 if ( $context ) {
303 $extra = [ 'embeddings' => $context['embeddings'] ];
304 }
305 $rawText = apply_filters( 'mwai_chatbot_reply', $rawText, $query, $params, $extra );
306
307 $restRes = [
308 'reply' => $rawText,
309 'images' => $reply->get_type() === 'images' ? $reply->results : null,
310 'usage' => $reply->usage
311 ];
312
313 // Process Reply
314 if ( $stream ) {
315 $this->stream_push( [
316 'type' => 'end',
317 'data' => json_encode([
318 'success' => true,
319 'reply' => $restRes['reply'],
320 'images' => $restRes['images'],
321 'usage' => $restRes['usage']
322 ])
323 ] );
324 die();
325 }
326 else {
327 return $restRes;
328 }
329
330 }
331 catch ( Exception $e ) {
332 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
333 if ( $stream ) {
334 $this->stream_push( [ 'type' => 'error', 'data' => $message ] );
335 die();
336 }
337 else {
338 throw $e;
339 }
340 }
341 }
342
343 public function stream_push( $data ) {
344 $out = "data: " . json_encode( $data );
345 echo $out;
346 echo "\n\n";
347 if (ob_get_level() > 0) {
348 ob_end_flush();
349 }
350 flush();
351 }
352
353 public function inject_chat() {
354 $params = $this->core->get_chatbot( $this->siteWideChatId );
355 $clean_params = [];
356 if ( !empty( $params ) ) {
357 $clean_params['window'] = true;
358 $clean_params['id'] = $this->siteWideChatId;
359 echo $this->chat_shortcode( $clean_params );
360 }
361 return null;
362 }
363
364 public function build_front_params( $botId, $customId ) {
365 $frontSystem = [
366 'botId' => $customId ? null : $botId,
367 'customId' => $customId,
368 'userData' => $this->core->get_user_data(),
369 'sessionId' => $this->core->get_session_id(),
370 'restNonce' => $this->core->get_nonce(),
371 'contextId' => get_the_ID(),
372 'pluginUrl' => MWAI_URL,
373 'restUrl' => untrailingslashit( get_rest_url() ),
374 'debugMode' => $this->core->get_option( 'debug_mode' ),
375 'typewriter' => $this->core->get_option( 'shortcode_chat_typewriter' ),
376 'speech_recognition' => $this->core->get_option( 'speech_recognition' ),
377 'speech_synthesis' => $this->core->get_option( 'speech_synthesis' ),
378 'stream' => $this->core->get_option( 'shortcode_chat_stream' ),
379 ];
380 return $frontSystem;
381 }
382
383 public function resolveBotInfo( &$atts )
384 {
385 $chatbot = null;
386 $botId = $atts['id'] ?? null;
387 $customId = $atts['custom_id'] ?? null;
388 if (!$botId && !$customId) {
389 $botId = "default";
390 }
391 if ( $botId ) {
392 $chatbot = $this->core->get_chatbot( $botId );
393 if (!$chatbot) {
394 $botId = $botId ?: 'N/A';
395 return [
396 'error' => "AI Engine: Chatbot '{$botId}' not found. If you meant to set an ID for your custom chatbot, please use 'custom_id' instead of 'id'.",
397 ];
398 }
399 }
400 $chatbot = $chatbot ?: $this->core->get_chatbot( 'default' );
401 if ( !empty( $customId ) ) {
402 $botId = null;
403 }
404 unset( $atts['id'] );
405 return [
406 'chatbot' => $chatbot,
407 'botId' => $botId,
408 'customId' => $customId,
409 ];
410 }
411
412 public function chat_shortcode( $atts ) {
413 $atts = empty( $atts ) ? [] : $atts;
414
415 // Let the user override the chatbot params
416 $atts = apply_filters( 'mwai_chatbot_params', $atts );
417
418 // Resolve the bot info
419 $resolvedBot = $this->resolveBotInfo( $atts, 'chatbot' );
420 if ( isset( $resolvedBot['error'] ) ) {
421 return $resolvedBot['error'];
422 }
423 $chatbot = $resolvedBot['chatbot'];
424 $botId = $resolvedBot['botId'];
425 $customId = $resolvedBot['customId'];
426
427 // Rename the keys of the atts into camelCase to match the internal params system.
428 $atts = array_map( function( $key, $value ) {
429 $key = str_replace( '_', ' ', $key );
430 $key = ucwords( $key );
431 $key = str_replace( ' ', '', $key );
432 $key = lcfirst( $key );
433 return [ $key => $value ];
434 }, array_keys( $atts ), $atts );
435 $atts = array_merge( ...$atts );
436
437 $frontParams = [];
438 foreach ( MWAI_CHATBOT_FRONT_PARAMS as $param ) {
439 if ( isset( $atts[$param] ) ) {
440 if ( $param === 'localMemory' ) {
441 $frontParams[$param] = $atts[$param] === 'true';
442 }
443 else {
444 $frontParams[$param] = $atts[$param];
445 }
446 }
447 else if ( isset( $chatbot[$param] ) ) {
448 $frontParams[$param] = $chatbot[$param];
449 }
450 }
451
452 // Server Params
453 // NOTE: We don't need the server params for the chatbot if there are no overrides, it means
454 // we are using the default or a specific chatbot.
455 $hasServerOverrides = count( array_intersect( array_keys( $atts ), MWAI_CHATBOT_SERVER_PARAMS ) ) > 0;
456 $serverParams = [];
457 if ( $hasServerOverrides ) {
458 foreach ( MWAI_CHATBOT_SERVER_PARAMS as $param ) {
459 if ( isset( $atts[$param] ) ) {
460 $serverParams[$param] = $atts[$param];
461 }
462 else {
463 $serverParams[$param] = $chatbot[$param] ?? null;
464 }
465 }
466 }
467
468 // Front Params
469 $frontSystem = $this->build_front_params( $botId, $customId );
470
471 // Clean Params
472 $frontParams = $this->clean_params( $frontParams );
473 $frontSystem = $this->clean_params( $frontSystem );
474 $serverParams = $this->clean_params( $serverParams );
475
476 // Server-side: Keep the System Params
477 if ( $hasServerOverrides ) {
478 if ( empty( $customId ) ) {
479 $customId = md5( json_encode( $serverParams ) );
480 $frontSystem['customId'] = $customId;
481 }
482 set_transient( 'mwai_custom_chatbot_' . $customId, $serverParams, 60 * 60 * 24 );
483 }
484
485 // Client-side: Prepare JSON for Front Params and System Params
486 $theme = isset( $frontParams['themeId'] ) ? $this->core->get_theme( $frontParams['themeId'] ) : null;
487 $jsonFrontParams = htmlspecialchars( json_encode( $frontParams ), ENT_QUOTES, 'UTF-8' );
488 $jsonFrontSystem = htmlspecialchars( json_encode( $frontSystem ), ENT_QUOTES, 'UTF-8' );
489 $jsonFrontTheme = htmlspecialchars( json_encode( $theme ), ENT_QUOTES, 'UTF-8' );
490 //$jsonAttributes = htmlspecialchars(json_encode($atts), ENT_QUOTES, 'UTF-8');
491
492 $this->enqueue_scripts();
493 return "<div class='mwai-chatbot-container' data-params='{$jsonFrontParams}' data-system='{$jsonFrontSystem}' data-theme='{$jsonFrontTheme}'></div>";
494 }
495
496 function shortcode_chat_discussions( $atts ) {
497 $atts = empty($atts) ? [] : $atts;
498
499 // Resolve the bot info
500 $resolvedBot = $this->resolveBotInfo( $atts );
501 if ( isset( $resolvedBot['error'] ) ) {
502 return $resolvedBot['error'];
503 }
504 $chatbot = $resolvedBot['chatbot'];
505 $botId = $resolvedBot['botId'];
506 $customId = $resolvedBot['customId'];
507
508 // Rename the keys of the atts into camelCase to match the internal params system.
509 $atts = array_map( function( $key, $value ) {
510 $key = str_replace( '_', ' ', $key );
511 $key = ucwords( $key );
512 $key = str_replace( ' ', '', $key );
513 $key = lcfirst( $key );
514 return [ $key => $value ];
515 }, array_keys( $atts ), $atts );
516 $atts = array_merge( ...$atts );
517
518 // Front Params
519 $frontParams = [];
520 foreach ( MWAI_DISCUSSIONS_FRONT_PARAMS as $param ) {
521 if ( isset( $atts[$param] ) ) {
522 $frontParams[$param] = $atts[$param];
523 }
524 else if ( isset( $chatbot[$param] ) ) {
525 $frontParams[$param] = $chatbot[$param];
526 }
527 }
528
529 // Server Params
530 $serverParams = [];
531 foreach ( MWAI_DISCUSSIONS_SERVER_PARAMS as $param ) {
532 if ( isset( $atts[$param] ) ) {
533 $serverParams[$param] = $atts[$param];
534 }
535 }
536
537 // Front System
538 $frontSystem = $this->build_front_params( $botId, $customId );
539
540 // Clean Params
541 $frontParams = $this->clean_params( $frontParams );
542 $frontSystem = $this->clean_params( $frontSystem );
543 $serverParams = $this->clean_params( $serverParams );
544
545 $theme = isset( $frontParams['themeId'] ) ? $this->core->get_theme( $frontParams['themeId'] ) : null;
546 $jsonFrontParams = htmlspecialchars( json_encode( $frontParams ), ENT_QUOTES, 'UTF-8' );
547 $jsonFrontSystem = htmlspecialchars( json_encode( $frontSystem ), ENT_QUOTES, 'UTF-8' );
548 $jsonFrontTheme = htmlspecialchars( json_encode( $theme ), ENT_QUOTES, 'UTF-8' );
549
550 return "<div class='mwai-discussions-container' data-params='{$jsonFrontParams}' data-system='{$jsonFrontSystem}' data-theme='{$jsonFrontTheme}'></div>";
551 }
552
553 function clean_params( &$params ) {
554 foreach ( $params as $param => $value ) {
555 if ( $param === 'restNonce' ) {
556 continue;
557 }
558 if ( empty( $value ) || is_array( $value ) ) {
559 continue;
560 }
561 $lowerCaseValue = strtolower( $value );
562 if ( $lowerCaseValue === 'true' || $lowerCaseValue === 'false' || is_bool( $value ) ) {
563 $params[$param] = filter_var( $value, FILTER_VALIDATE_BOOLEAN );
564 }
565 else if ( is_numeric( $value ) ) {
566 $params[$param] = filter_var( $value, FILTER_VALIDATE_FLOAT );
567 }
568 }
569 return $params;
570 }
571
572 }
573