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