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