PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 1.8.8
AI Engine – The Chatbot, AI Framework & MCP for WordPress v1.8.8
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 / core.php
ai-engine / classes Last commit date
engines 2 years ago modules 3 years ago queries 2 years ago admin.php 2 years ago api.php 2 years ago core.php 2 years ago init.php 3 years ago reply.php 2 years ago rest.php 3 years ago
core.php
612 lines
1 <?php
2
3 require_once( MWAI_PATH . '/vendor/autoload.php' );
4 require_once( MWAI_PATH . '/constants/init.php' );
5
6 use Rahul900day\Gpt3Encoder\Encoder;
7
8 define( 'MWAI_IMG_WAND', MWAI_URL . '/images/wand.png' );
9 define( 'MWAI_IMG_WAND_HTML', "<img style='height: 22px; margin-bottom: -5px; margin-right: 8px;'
10 src='" . MWAI_IMG_WAND . "' alt='AI Wand' />" );
11 define( 'MWAI_IMG_WAND_HTML_XS', "<img style='height: 16px; margin-bottom: -2px;'
12 src='" . MWAI_IMG_WAND . "' alt='AI Wand' />" );
13
14 class Meow_MWAI_Core
15 {
16 public $admin = null;
17 public $is_rest = false;
18 public $is_cli = false;
19 public $site_url = null;
20 public $ai = null;
21 private $option_name = 'mwai_options';
22 private $themes_option_name = 'mwai_themes';
23 private $chatbots_option_name = 'mwai_chatbots';
24 private $nonce = null;
25 public $defaultChatbotParams = MWAI_CHATBOT_PARAMS;
26
27 // Cached
28 private $options = null;
29
30 public function __construct() {
31 $this->site_url = get_site_url();
32 $this->is_rest = MeowCommon_Helpers::is_rest();
33 $this->is_cli = defined( 'WP_CLI' );
34 $this->ai = new Meow_MWAI_Engines_Core( $this );
35 add_action( 'plugins_loaded', array( $this, 'init' ) );
36 }
37
38 function init() {
39 global $mwai;
40 $mwai = new Meow_MWAI_API();
41 new Meow_MWAI_Modules_Security( $this );
42 if ( $this->is_rest ) {
43 new Meow_MWAI_Rest( $this );
44 }
45 if ( is_admin() ) {
46 new Meow_MWAI_Admin( $this );
47 new Meow_MWAI_Modules_Assistants( $this );
48 }
49 if ( $this->get_option( 'shortcode_chat' ) ) {
50 new Meow_MWAI_Modules_Chatbot();
51 new Meow_MWAI_Modules_Chatbot_Legacy();
52 new Meow_MWAI_Modules_Discussions();
53 }
54
55 // Advanced core
56 if ( class_exists( 'MeowPro_MWAI_Core' ) ) {
57 new MeowPro_MWAI_Core( $this );
58 }
59
60 // Dynamic max tokens
61 if ( $this->get_option( 'dynamic_max_tokens' ) ) {
62 add_filter( 'mwai_estimate_tokens', array( $this, 'dynamic_max_tokens' ), 10, 2 );
63 }
64 }
65
66 #region Roles & Capabilities
67
68 function can_access_settings() {
69 return apply_filters( 'mwai_allow_setup', current_user_can( 'manage_options' ) );
70 }
71
72 function can_access_features() {
73 $editor_or_admin = current_user_can( 'editor' ) || current_user_can( 'administrator' );
74 return apply_filters( 'mwai_allow_usage', $editor_or_admin );
75 }
76
77 function can_access_public_api() {
78 return apply_filters( 'mwai_allow_public_api', '__return_true' );
79 }
80
81 #endregion
82
83 #region Text-Related Helpers
84
85 // Clean the text perfectly, resolve shortcodes, etc, etc.
86 function cleanText( $rawText = "" ) {
87 $text = html_entity_decode( $rawText );
88 $text = wp_strip_all_tags( $text );
89 $text = preg_replace( '/[\r\n]+/', "\n", $text );
90 return $text . " ";
91
92 // Before simplification:
93 // $text = strip_tags( $rawText );
94 // $text = strip_shortcodes( $text );
95 // $text = html_entity_decode( $text );
96 // $text = preg_replace( '/[\r\n]+/', "\n", $text );
97 // $sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
98 // foreach ( $sentences as $key => $sentence ) {
99 // $sentences[$key] = trim( $sentence );
100 // }
101 // $text = implode( " ", $sentences );
102 // $text = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $text );
103 // return $text . " ";
104 }
105
106 // Make sure there are no duplicate sentences, and keep the length under a maximum length.
107 function cleanSentences( $text, $maxTokens = null ) {
108 //$sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
109 $maxTokens = $maxTokens ? $maxTokens : $this->get_option( 'context_max_tokens', 1024 );
110 $sentences = preg_split('/(?<=[.?!。.!?])+/u', $text);
111 $hashes = array();
112 $uniqueSentences = array();
113 $length = 0;
114 foreach ( $sentences as $sentence ) {
115 $sentence = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $sentence );
116 $hash = md5( $sentence );
117 if ( !in_array( $hash, $hashes ) ) {
118 $tokensCount = apply_filters( 'mwai_estimate_tokens', 0, $sentence );
119 if ( $length + $tokensCount > $maxTokens ) {
120 continue;
121 }
122 $hashes[] = $hash;
123 $uniqueSentences[] = $sentence;
124 $length += $tokensCount;
125 }
126 }
127 $freshText = implode( " ", $uniqueSentences );
128 $freshText = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $freshText );
129 return $freshText;
130 }
131
132 function getCleanPostContent( $postId ) {
133 $post = get_post( $postId );
134 if ( !$post ) {
135 return false;
136 }
137 $text = $post->post_content;
138 $pattern = '/\[mwai_.*?\]/';
139 $text = preg_replace( $pattern, '', $text );
140 if ( $this->get_option( 'resolve_shortcodes' ) ) {
141 $text = apply_filters( 'the_content', $text );
142 }
143 $text = $this->cleanText( $text );
144 $text = $this->cleanSentences( $text );
145 return $text;
146 }
147
148 function markdown_to_html( $content ) {
149 $Parsedown = new Parsedown();
150 $content = $Parsedown->text( $content );
151 return $content;
152 }
153
154 function get_post_language( $postId ) {
155 $locale = get_locale();
156 $code = strtolower( substr( $locale, 0, 2 ) );
157 $humanLanguage = strtr( $code, MWAI_ALL_LANGUAGES );
158 $lang = apply_filters( 'wpml_post_language_details', null, $postId );
159 if ( !empty( $lang ) ) {
160 $locale = $lang['locale'];
161 $humanLanguage = $lang['display_name'];
162 }
163 return strtolower( "$locale ($humanLanguage)" );
164 }
165 #endregion
166
167 #region Users/Sessions Helpers
168
169 function get_nonce() {
170 if ( !is_user_logged_in() ) {
171 return null;
172 }
173 if ( isset( $this->nonce ) ) {
174 return $this->nonce;
175 }
176 $this->nonce = wp_create_nonce( 'wp_rest' );
177 return $this->nonce;
178 }
179
180 function get_session_id() {
181 if ( isset( $_COOKIE['mwai_session_id'] ) ) {
182 return $_COOKIE['mwai_session_id'];
183 }
184 return "N/A";
185 }
186
187 // Get the UserID from the data, or from the current user
188 function get_user_id( $data = null ) {
189 if ( isset( $data ) && isset( $data['userId'] ) ) {
190 return (int)$data['userId'];
191 }
192 if ( is_user_logged_in() ) {
193 $current_user = wp_get_current_user();
194 if ( $current_user->ID > 0 ) {
195 return $current_user->ID;
196 }
197 }
198 return null;
199 }
200
201 function getUserData() {
202 $user = wp_get_current_user();
203 if ( empty( $user ) || empty( $user->ID ) ) {
204 return null;
205 }
206 $placeholders = array(
207 'FIRST_NAME' => get_user_meta( $user->ID, 'first_name', true ),
208 'LAST_NAME' => get_user_meta( $user->ID, 'last_name', true ),
209 'USER_LOGIN' => isset( $user ) && isset($user->data) && isset( $user->data->user_login ) ?
210 $user->data->user_login : null,
211 'DISPLAY_NAME' => isset( $user ) && isset( $user->data ) && isset( $user->data->display_name ) ?
212 $user->data->display_name : null,
213 'AVATAR_URL' => get_avatar_url( get_current_user_id() ),
214 );
215 return $placeholders;
216 }
217
218 function get_ip_address( $data = null ) {
219 if ( isset( $data ) && isset( $data['ip'] ) ) {
220 $data['ip'] = (string)$data['ip'];
221 }
222 else {
223 if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
224 $data['ip'] = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
225 }
226 else if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
227 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_CLIENT_IP'] );
228 }
229 else if ( isset( $_SERVER['HTTP_X_FORWARDED_ FOR'] ) ) {
230 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_X_FORWARDED_FOR'] );
231 }
232 }
233 $ip = apply_filters( 'mwai_get_ip_address', $data['ip'] );
234 return $ip;
235 }
236
237 #endregion
238
239 #region Other Helpers
240
241 function isUrl( $url ) {
242 return strpos( $url, 'http' ) === 0 ? true : false;
243 }
244
245 function getPostTypes() {
246 $excluded = array( 'attachment', 'revision', 'nav_menu_item' );
247 $post_types = array();
248 $types = get_post_types( [], 'objects' );
249
250 // Let's get the Post Types that are enabled for Embeddings Sync
251 $embeddingsSettings = $this->get_option( 'embeddings' );
252 $syncPostTypes = isset( $embeddingsSettings['syncPostTypes'] ) ? $embeddingsSettings['syncPostTypes'] : [];
253
254 foreach ( $types as $type ) {
255 $forced = in_array( $type->name, $syncPostTypes );
256 // Should not be excluded.
257 if ( !$forced && in_array( $type->name, $excluded ) ) {
258 continue;
259 }
260 // Should be public.
261 if ( !$forced && !$type->public ) {
262 continue;
263 }
264 $post_types[] = array(
265 'name' => $type->labels->name,
266 'type' => $type->name,
267 );
268 }
269
270 // Let's get the Post Types that are enabled for Embeddings Sync
271 $embeddingsSettings = $this->get_option( 'embeddings' );
272 $syncPostTypes = isset( $embeddingsSettings['syncPostTypes'] ) ? $embeddingsSettings['syncPostTypes'] : [];
273
274 return $post_types;
275 }
276
277 function getCleanPost( $post ) {
278 if ( is_object( $post ) ) {
279 $post = (array)$post;
280 }
281 $language = $this->get_post_language( $post['ID'] );
282 $content = apply_filters( 'mwai_pre_post_content', $post['post_content'], $post['ID'] );
283 $content = $this->cleanText( $content );
284 $content = apply_filters( 'mwai_post_content', $content, $post['ID'] );
285 $title = $post['post_title'];
286 $excerpt = $post['post_excerpt'];
287 $url = get_permalink( $post['ID'] );
288 $checksum = wp_hash( $content . $title . $url );
289 return [
290 'postId' => $post['ID'],
291 'title' => $title,
292 'content' => $content,
293 'excerpt' => $excerpt,
294 'url' => $url,
295 'language' => $language,
296 'checksum' => $checksum,
297 ];
298 }
299 #endregion
300
301 #region Usage & Costs
302
303 public function dynamic_max_tokens( $tokens, $text ) {
304 // Approximation (fast, no lib)
305 $asciiCount = 0;
306 $nonAsciiCount = 0;
307 for ( $i = 0; $i < mb_strlen( $text ); $i++ ) {
308 $char = mb_substr( $text, $i, 1 );
309 if ( ord( $char ) < 128 ) {
310 $asciiCount++;
311 }
312 else {
313 $nonAsciiCount++;
314 }
315 }
316 $asciiTokens = $asciiCount / 3.5;
317 $nonAsciiTokens = $nonAsciiCount * 2.5;
318 $tokens = $asciiTokens + $nonAsciiTokens;
319
320 // More exact (slower, and lib)
321 if ( PHP_VERSION_ID >= 70400 && function_exists( 'mb_convert_encoding' ) ) {
322 try {
323 $token_array = Encoder::encode( $text );
324 if ( !empty( $token_array ) ) {
325 $tokens = count( $token_array );
326 }
327 }
328 catch ( Exception $e ) {
329 error_log( $e->getMessage() );
330 }
331 }
332
333 $tokens = $tokens;
334 return (int)$tokens;
335 }
336
337 public function recordTokensUsage( $model, $prompt_tokens, $completion_tokens = 0 ) {
338 if ( !is_numeric( $prompt_tokens ) ) {
339 throw new Exception( 'Record usage: prompt_tokens is not a number.' );
340 }
341 if ( !is_numeric( $completion_tokens ) ) {
342 $completion_tokens = 0;
343 }
344 if ( !$model ) {
345 throw new Exception( 'Record usage: model is missing.' );
346 }
347 $usage = $this->get_option( 'openai_usage' );
348 $month = date( 'Y-m' );
349 if ( !isset( $usage[$month] ) ) {
350 $usage[$month] = array();
351 }
352 if ( !isset( $usage[$month][$model] ) ) {
353 $usage[$month][$model] = array(
354 'prompt_tokens' => 0,
355 'completion_tokens' => 0,
356 'total_tokens' => 0
357 );
358 }
359 $usage[$month][$model]['prompt_tokens'] += $prompt_tokens;
360 $usage[$month][$model]['completion_tokens'] += $completion_tokens;
361 $usage[$month][$model]['total_tokens'] += $prompt_tokens + $completion_tokens;
362 $this->update_option( 'openai_usage', $usage );
363 return [
364 'prompt_tokens' => $prompt_tokens,
365 'completion_tokens' => $completion_tokens,
366 'total_tokens' => $prompt_tokens + $completion_tokens
367 ];
368 }
369
370 public function recordAudioUsage( $model, $seconds ) {
371 if ( !is_numeric( $seconds ) ) {
372 throw new Exception( 'Record usage: seconds is not a number.' );
373 }
374 if ( !$model ) {
375 throw new Exception( 'Record usage: model is missing.' );
376 }
377 $usage = $this->get_option( 'openai_usage' );
378 $month = date( 'Y-m' );
379 if ( !isset( $usage[$month] ) ) {
380 $usage[$month] = array();
381 }
382 if ( !isset( $usage[$month][$model] ) ) {
383 $usage[$month][$model] = array(
384 'seconds' => 0
385 );
386 }
387 $usage[$month][$model]['seconds'] += $seconds;
388 $this->update_option( 'openai_usage', $usage );
389 return [
390 'seconds' => $seconds
391 ];
392 }
393
394 public function recordImagesUsage( $model, $resolution, $images ) {
395 if ( !$model || !$resolution || !$images ) {
396 throw new Exception( 'Missing parameters for record_image_usage.' );
397 }
398 $usage = $this->get_option( 'openai_usage' );
399 $month = date( 'Y-m' );
400 if ( !isset( $usage[$month] ) ) {
401 $usage[$month] = array();
402 }
403 if ( !isset( $usage[$month][$model] ) ) {
404 $usage[$month][$model] = array(
405 'resolution' => array(),
406 'images' => 0
407 );
408 }
409 if ( !isset( $usage[$month][$model]['resolution'][$resolution] ) ) {
410 $usage[$month][$model]['resolution'][$resolution] = 0;
411 }
412 $usage[$month][$model]['resolution'][$resolution] += $images;
413 $usage[$month][$model]['images'] += $images;
414 $this->update_option( 'openai_usage', $usage );
415 return [
416 'resolution' => $resolution,
417 'images' => $images
418 ];
419 }
420
421 #endregion
422
423 #region Streaming
424 public function stream_push( $data ) {
425 $out = "data: " . json_encode( $data );
426 echo $out;
427 echo "\n\n";
428 if ( ob_get_level() > 0 ) {
429 ob_end_flush();
430 }
431 flush();
432 }
433 #endregion
434
435 #region Options
436 function getThemes() {
437 $themes = get_option( $this->themes_option_name, [] );
438 $themes = empty( $themes ) ? [] : $themes;
439
440 $internalThemes = [
441 'chatgpt' => [
442 'type' => 'internal', 'name' => 'ChatGPT', 'themeId' => 'chatgpt',
443 'settings' => [], 'style' => ""
444 ],
445 'messages' => [
446 'type' => 'internal', 'name' => 'Messages', 'themeId' => 'messages',
447 'settings' => [], 'style' => ""
448 ],
449 ];
450 $customThemes = [];
451 foreach ( $themes as $theme ) {
452 if ( isset( $internalThemes[$theme['themeId']] ) ) {
453 $internalThemes[$theme['themeId']] = $theme;
454 continue;
455 }
456 $customThemes[] = $theme;
457 }
458 return array_merge(array_values($internalThemes), $customThemes);
459 }
460
461 function updateThemes( $themes ) {
462 update_option( $this->themes_option_name, $themes );
463 return $themes;
464 }
465
466 function getChatbots() {
467 $chatbots = get_option( $this->chatbots_option_name, [] );
468 $hasChanges = false;
469 if ( empty( $chatbots ) ) {
470 $chatbots = [ array_merge( MWAI_CHATBOT_DEFAULT_PARAMS, ['name' => 'Default', 'botId' => 'default' ] ) ];
471 }
472 foreach ( $chatbots as &$chatbot ) {
473 foreach ( MWAI_CHATBOT_DEFAULT_PARAMS as $key => $value ) {
474 // Use default value if not set.
475 if ( !isset( $chatbot[$key] ) ) {
476 $chatbot[$key] = $value;
477 }
478 }
479 // After September 2023, let's remove this if statement.
480 if ( isset( $chatbot['chatId'] ) ) {
481 $chatbot['botId'] = $chatbot['chatId'];
482 unset( $chatbot['chatId'] );
483 $hasChanges = true;
484 }
485 // After September 2023, let's remove this if statement.
486 if ( empty( $chatbot['botId'] ) && $chatbot['name'] === 'default' ) {
487 $chatbot['botId'] = sanitize_title( $chatbot['name'] );
488 $hasChanges = true;
489 }
490 }
491 if ( $hasChanges ) {
492 update_option( $this->chatbots_option_name, $chatbots );
493 }
494 return $chatbots;
495 }
496
497 function getChatbot( $botId ) {
498 $chatbots = $this->getChatbots();
499 foreach ( $chatbots as $chatbot ) {
500 if ( $chatbot['botId'] === (string)$botId ) {
501 // Somehow, the default was set to "openai" when creating a new chatbot, but that overrided
502 // the default value in the Settings. It should be always empty here (except if we add this
503 // into the Settings of the chatbot).
504 $chatbot['service'] = null;
505 return $chatbot;
506 }
507 }
508 return null;
509 }
510
511 function getTheme( $themeId ) {
512 $themes = $this->getThemes();
513 foreach ( $themes as $theme ) {
514 if ( $theme['themeId'] === $themeId ) {
515 return $theme;
516 }
517 }
518 return null;
519 }
520
521 function updateChatbots( $chatbots ) {
522 $htmlFields = [ 'textCompliance', 'aiName', 'userName', 'startSentence' ];
523 $whiteSpacedFields = [ 'context' ];
524 foreach ( $chatbots as &$chatbot ) {
525 foreach ( $chatbot as $key => &$value ) {
526 if ( in_array( $key, $htmlFields ) ) {
527 $value = wp_kses_post( $value );
528 }
529 else if ( in_array( $key, $whiteSpacedFields ) ) {
530 $value = sanitize_textarea_field( $value );
531 }
532 else {
533 $value = sanitize_text_field( $value );
534 }
535 }
536 }
537
538 update_option( $this->chatbots_option_name, $chatbots );
539 return $chatbots;
540 }
541
542 function get_all_options( $force = false ) {
543 // We could cache options this way, but if we do, the apply_filters seems to be called too early.
544 // That causes issues with the mwai_languages filter.
545 // if ( !$force && !is_null( $this->options ) ) {
546 // return $this->options;
547 // }
548 $options = get_option( $this->option_name, [] );
549 $options = $this->sanitize_options( $options );
550 foreach ( MWAI_OPTIONS as $key => $value ) {
551 if ( !isset( $options[$key] ) ) {
552 $options[$key] = $value;
553 }
554 if ( $key === 'languages' ) {
555 // NOTE: If we decide to make a set of options for languages, we can keep it in the settings
556 $options[$key] = apply_filters( 'mwai_languages', MWAI_LANGUAGES );
557 }
558 }
559 $options['shortcode_chat_default_params'] = MWAI_CHATBOT_PARAMS;
560 $options['chatbot_defaults'] = MWAI_CHATBOT_DEFAULT_PARAMS;
561 $options['default_limits'] = MWAI_LIMITS;
562 $options['openai_models'] = Meow_MWAI_Engines_OpenAI::get_openai_models();
563 $this->options = $options;
564 return $options;
565 }
566
567 // Sanitize options when we update the plugi or perform some updates
568 // if we change the structure of the options.
569 function sanitize_options( $options ) {
570 $needs_update = false;
571
572 // This upgrades namespace to multi-namespaces (June 2023)
573 // After January 2024, let's remove this.
574 if ( isset( $options['pinecone'] ) && isset( $options['pinecone']['namespace'] ) ) {
575 $options['pinecone']['namespaces'] = [ $options['pinecone']['namespace'] ];
576 unset( $options['pinecone']['namespace'] );
577 $needs_update = true;
578 }
579
580 if ( $needs_update ) {
581 update_option( $this->option_name, $options, false );
582 }
583
584 return $options;
585 }
586
587 function update_options( $options ) {
588 if ( !update_option( $this->option_name, $options, false ) ) {
589 return false;
590 }
591 $options = $this->get_all_options( true );
592 return $options;
593 }
594
595 function update_option( $option, $value ) {
596 $options = $this->get_all_options( true );
597 $options[$option] = $value;
598 return $this->update_options( $options );
599 }
600
601 function get_option( $option, $default = null ) {
602 $options = $this->get_all_options();
603 return $options[$option] ?? $default;
604 }
605
606 function reset_options() {
607 return $this->update_options( MWAI_OPTIONS );
608 }
609 #endregion
610 }
611
612 ?>