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