PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 1.4.7
AI Engine – The Chatbot, AI Framework & MCP for WordPress v1.4.7
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
modules 3 years ago admin.php 3 years ago ai.php 3 years ago answer.php 3 years ago api.php 3 years ago core.php 3 years ago init.php 3 years ago openai.php 3 years ago query.php 3 years ago queryembed.php 3 years ago queryimage.php 3 years ago querytext.php 3 years ago querytranscribe.php 3 years ago rest.php 3 years ago security.php 3 years ago
core.php
472 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 class Meow_MWAI_Core
14 {
15 public $admin = null;
16 public $is_rest = false;
17 public $is_cli = false;
18 public $site_url = null;
19 public $ai = null;
20 private $option_name = 'mwai_options';
21 private $themes_option_name = 'mwai_themes';
22 private $chatbots_option_name = 'mwai_chatbots';
23 public $defaultChatbotParams = MWAI_CHATBOT_PARAMS;
24
25 public function __construct() {
26 $this->site_url = get_site_url();
27 $this->is_rest = MeowCommon_Helpers::is_rest();
28 $this->is_cli = defined( 'WP_CLI' ) && WP_CLI;
29 $this->ai = new Meow_MWAI_AI( $this );
30 add_action( 'plugins_loaded', array( $this, 'init' ) );
31 }
32
33 function init() {
34 global $mwai;
35 $mwai = new Meow_MWAI_API();
36 new Meow_MWAI_Security( $this );
37 if ( $this->is_rest ) {
38 new Meow_MWAI_Rest( $this );
39 }
40 if ( is_admin() ) {
41 new Meow_MWAI_Admin( $this );
42 new Meow_MWAI_Modules_Assistants( $this );
43 }
44 if ( $this->get_option( 'shortcode_chat' ) ) {
45 new Meow_MWAI_Modules_Chatbot();
46 new Meow_MWAI_Modules_Chatbot_Legacy();
47 new Meow_MWAI_Modules_Discussions();
48 }
49
50 // Advanced core
51 if ( class_exists( 'MeowPro_MWAI_Core' ) ) {
52 new MeowPro_MWAI_Core( $this );
53 }
54
55 // Dynamic max tokens
56 if ( $this->get_option( 'dynamic_max_tokens' ) ) {
57 add_filter( 'mwai_estimate_tokens', array( $this, 'dynamic_max_tokens' ), 10, 2 );
58 }
59 }
60
61 #region Roles & Capabilities
62
63 function can_access_settings() {
64 return apply_filters( 'mwai_allow_setup', current_user_can( 'manage_options' ) );
65 }
66
67 function can_access_features() {
68 $editor_or_admin = current_user_can( 'editor' ) || current_user_can( 'administrator' );
69 return apply_filters( 'mwai_allow_usage', $editor_or_admin );
70 }
71
72 #endregion
73
74 #region Text-Related Helpers
75
76 // Clean the text perfectly, resolve shortcodes, etc, etc.
77 function cleanText( $rawText = "" ) {
78 $text = html_entity_decode( $rawText );
79 $text = wp_strip_all_tags( $text );
80 $text = preg_replace( '/[\r\n]+/', "\n", $text );
81 return $text . " ";
82
83 // Before simplification:
84 // $text = strip_tags( $rawText );
85 // $text = strip_shortcodes( $text );
86 // $text = html_entity_decode( $text );
87 // $text = preg_replace( '/[\r\n]+/', "\n", $text );
88 // $sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
89 // foreach ( $sentences as $key => $sentence ) {
90 // $sentences[$key] = trim( $sentence );
91 // }
92 // $text = implode( " ", $sentences );
93 // $text = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $text );
94 // return $text . " ";
95 }
96
97 // Make sure there are no duplicate sentences, and keep the length under a maximum length.
98 function cleanSentences( $text, $maxTokens = 512 ) {
99 //$sentences = preg_split( '/(?<=[.?!])(?=[a-zA-Z ])/', $text );
100 $sentences = preg_split('/(?<=[.?!。.!?])+/u', $text);
101 $hashes = array();
102 $uniqueSentences = array();
103 $length = 0;
104 foreach ( $sentences as $sentence ) {
105 $sentence = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $sentence );
106 $hash = md5( $sentence );
107 if ( !in_array( $hash, $hashes ) ) {
108 $tokensCount = apply_filters( 'mwai_estimate_tokens', 0, $sentence );
109 if ( $length + $tokensCount > $maxTokens ) {
110 continue;
111 }
112 $hashes[] = $hash;
113 $uniqueSentences[] = $sentence;
114 $length += $tokensCount;
115 }
116 }
117 $freshText = implode( " ", $uniqueSentences );
118 $freshText = preg_replace( '/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $freshText );
119 return $freshText;
120 }
121
122 function getCleanPostContent( $postId ) {
123 $post = get_post( $postId );
124 if ( !$post ) {
125 return false;
126 }
127 $text = $post->post_content;
128 $pattern = '/\[mwai_.*?\]/';
129 $text = preg_replace( $pattern, '', $text );
130 if ( $this->get_option( 'resolve_shortcodes' ) ) {
131 $text = apply_filters( 'the_content', $text );
132 }
133 $text = $this->cleanText( $text );
134 $text = $this->cleanSentences( $text );
135 return $text;
136 }
137
138 function markdown_to_html( $content ) {
139 $Parsedown = new Parsedown();
140 $content = $Parsedown->text( $content );
141 return $content;
142 }
143
144 function get_post_language( $postId ) {
145 $locale = get_locale();
146 $code = strtolower( substr( $locale, 0, 2 ) );
147 $humanLanguage = strtr( $code, MWAI_ALL_LANGUAGES );
148 $lang = apply_filters( 'wpml_post_language_details', null, $postId );
149 if ( !empty( $lang ) ) {
150 $locale = $lang['locale'];
151 $humanLanguage = $lang['display_name'];
152 }
153 return strtolower( "$locale ($humanLanguage)" );
154 }
155 #endregion
156
157 #region Users/Sessions Helpers
158
159 function get_session_id() {
160 if ( isset( $_COOKIE['mwai_session_id'] ) ) {
161 return $_COOKIE['mwai_session_id'];
162 }
163 return "N/A";
164 }
165
166 // Get the UserID from the data, or from the current user
167 function get_user_id( $data = null ) {
168 if ( isset( $data ) && isset( $data['userId'] ) ) {
169 return (int)$data['userId'];
170 }
171 if ( is_user_logged_in() ) {
172 $current_user = wp_get_current_user();
173 if ( $current_user->ID > 0 ) {
174 return $current_user->ID;
175 }
176 }
177 return null;
178 }
179
180 function getUserData() {
181 $user = wp_get_current_user();
182 $placeholders = array(
183 'FIRST_NAME' => get_user_meta($user->ID, 'first_name', true),
184 'LAST_NAME' => get_user_meta($user->ID, 'last_name', true),
185 'USER_LOGIN' => $user->data->user_login,
186 'DISPLAY_NAME' => $user->data->display_name,
187 'AVATAR_URL' => get_avatar_url( get_current_user_id() ),
188 );
189 return $placeholders;
190 }
191
192 function get_ip_address( $data = null ) {
193 if ( isset( $data ) && isset( $data['ip'] ) ) {
194 $data['ip'] = (string)$data['ip'];
195 }
196 else {
197 if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
198 $data['ip'] = sanitize_text_field( $_SERVER['REMOTE_ADDR'] );
199 }
200 else if ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
201 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_CLIENT_IP'] );
202 }
203 else if ( isset( $_SERVER['HTTP_X_FORWARDED_ FOR'] ) ) {
204 $data['ip'] = sanitize_text_field( $_SERVER['HTTP_X_FORWARDED_FOR'] );
205 }
206 }
207 $ip = apply_filters( 'mwai_get_ip_address', $data['ip'] );
208 return $ip;
209 }
210
211 #endregion
212
213 #region Other Helpers
214
215 function isUrl( $url ) {
216 return strpos( $url, 'http' ) === 0 ? true : false;
217 }
218
219 function getPostTypes() {
220 $excluded = array( 'attachment', 'revision', 'nav_menu_item' );
221 $post_types = array();
222 $types = get_post_types( array( 'public' => true ), 'objects' );
223 foreach ( $types as $type ) {
224 if ( in_array( $type->name, $excluded ) ) {
225 continue;
226 }
227 $post_types[] = array(
228 'name' => $type->labels->name,
229 'type' => $type->name,
230 );
231 }
232 return $post_types;
233 }
234
235 function getCleanPost( $post ) {
236 if ( is_object( $post ) ) {
237 $post = (array)$post;
238 }
239 $language = $this->get_post_language( $post['ID'] );
240 $content = apply_filters( 'mwai_pre_post_content', $post['post_content'], $post['ID'] );
241 $content = $this->cleanText( $content );
242 $content = apply_filters( 'mwai_post_content', $content, $post['ID'] );
243 $title = $post['post_title'];
244 $excerpt = $post['post_excerpt'];
245 $url = get_permalink( $post['ID'] );
246 $checksum = wp_hash( $content . $title . $url );
247 return [
248 'postId' => $post['ID'],
249 'title' => $title,
250 'content' => $content,
251 'excerpt' => $excerpt,
252 'url' => $url,
253 'language' => $language,
254 'checksum' => $checksum,
255 ];
256 }
257
258 #endregion
259
260 #region Usage & Costs
261
262 public function dynamic_max_tokens( $tokens, $text ) {
263 // Approximation (fast, no lib)
264 $asciiCount = 0;
265 $nonAsciiCount = 0;
266 for ( $i = 0; $i < mb_strlen( $text ); $i++ ) {
267 $char = mb_substr( $text, $i, 1 );
268 if ( ord( $char ) < 128 ) {
269 $asciiCount++;
270 }
271 else {
272 $nonAsciiCount++;
273 }
274 }
275 $asciiTokens = $asciiCount / 3.5;
276 $nonAsciiTokens = $nonAsciiCount * 2.5;
277 $tokens = $asciiTokens + $nonAsciiTokens;
278
279 // More exact (slower, and lib)
280 if ( PHP_VERSION_ID >= 70400 && function_exists( 'mb_convert_encoding' ) ) {
281 try {
282 $token_array = Encoder::encode( $text );
283 if ( !empty( $token_array ) ) {
284 $tokens = count( $token_array );
285 }
286 }
287 catch ( Exception $e ) {
288 error_log( $e->getMessage() );
289 }
290 }
291
292 $tokens = $tokens;
293 return (int)$tokens;
294 }
295
296 public function record_tokens_usage( $model, $prompt_tokens, $completion_tokens = 0 ) {
297 if ( !is_numeric( $prompt_tokens ) ) {
298 throw new Exception( 'Record usage: prompt_tokens is not a number.' );
299 }
300 if ( !is_numeric( $completion_tokens ) ) {
301 $completion_tokens = 0;
302 }
303 if ( !$model ) {
304 throw new Exception( 'Record usage: model is missing.' );
305 }
306 $usage = $this->get_option( 'openai_usage' );
307 $month = date( 'Y-m' );
308 if ( !isset( $usage[$month] ) ) {
309 $usage[$month] = array();
310 }
311 if ( !isset( $usage[$month][$model] ) ) {
312 $usage[$month][$model] = array(
313 'prompt_tokens' => 0,
314 'completion_tokens' => 0,
315 'total_tokens' => 0
316 );
317 }
318 $usage[$month][$model]['prompt_tokens'] += $prompt_tokens;
319 $usage[$month][$model]['completion_tokens'] += $completion_tokens;
320 $usage[$month][$model]['total_tokens'] += $prompt_tokens + $completion_tokens;
321 $this->update_option( 'openai_usage', $usage );
322 return [
323 'prompt_tokens' => $prompt_tokens,
324 'completion_tokens' => $completion_tokens,
325 'total_tokens' => $prompt_tokens + $completion_tokens
326 ];
327 }
328
329 public function record_images_usage( $model, $resolution, $images ) {
330 if ( !$model || !$resolution || !$images ) {
331 throw new Exception( 'Missing parameters for record_image_usage.' );
332 }
333 $usage = $this->get_option( 'openai_usage' );
334 $month = date( 'Y-m' );
335 if ( !isset( $usage[$month] ) ) {
336 $usage[$month] = array();
337 }
338 if ( !isset( $usage[$month][$model] ) ) {
339 $usage[$month][$model] = array(
340 'resolution' => array(),
341 'images' => 0
342 );
343 }
344 if ( !isset( $usage[$month][$model]['resolution'][$resolution] ) ) {
345 $usage[$month][$model]['resolution'][$resolution] = 0;
346 }
347 $usage[$month][$model]['resolution'][$resolution] += $images;
348 $usage[$month][$model]['images'] += $images;
349 $this->update_option( 'openai_usage', $usage );
350 return [
351 'resolution' => $resolution,
352 'images' => $images
353 ];
354 }
355
356 #endregion
357
358 #region Options
359 function getThemes() {
360 $themes = get_option( $this->themes_option_name, [] );
361 if ( empty( $themes ) ) {
362 $themes = [ [
363 'type' => 'internal',
364 'name' => 'ChatGPT',
365 'themeId' => 'chatgpt',
366 'settings' => [],
367 'style' => ""
368 ] ];
369 }
370 return $themes;
371 }
372
373 function updateThemes( $themes ) {
374 update_option( $this->themes_option_name, $themes );
375 return $themes;
376 }
377
378 function getChatbots() {
379 $chatbots = get_option( $this->chatbots_option_name, [] );
380 if ( empty( $chatbots ) ) {
381 $chatbots = [ array_merge( MWAI_CHATBOT_DEFAULT_PARAMS, ['name' => 'Default', 'chatId' => 'default' ] ) ];
382 }
383 foreach ( $chatbots as $chatbot ) {
384 foreach ( MWAI_CHATBOT_DEFAULT_PARAMS as $key => $value ) {
385 if ( !isset( $chatbot[$key] ) ) {
386 $chatbot[$key] = $value;
387 }
388 }
389 }
390 return $chatbots;
391 }
392
393 function getChatbot( $chatId ) {
394 $chatbots = $this->getChatbots();
395 foreach ( $chatbots as $chatbot ) {
396 if ( $chatbot['chatId'] === $chatId ) {
397 return $chatbot;
398 }
399 }
400 return null;
401 }
402
403 function getTheme( $themeId ) {
404 $themes = $this->getThemes();
405 foreach ( $themes as $theme ) {
406 if ( $theme['themeId'] === $themeId ) {
407 return $theme;
408 }
409 }
410 return null;
411 }
412
413 function updateChatbots( $chatbots ) {
414 update_option( $this->chatbots_option_name, $chatbots );
415 return $chatbots;
416 }
417
418 function get_all_options() {
419 $options = get_option( $this->option_name, null );
420 foreach ( MWAI_OPTIONS as $key => $value ) {
421 if ( !isset( $options[$key] ) ) {
422 $options[$key] = $value;
423 }
424 if ( $key === 'languages' ) {
425 // TODO: If we decide to make a set of options for languages, we can keep it in the settings
426 $options[$key] = MWAI_LANGUAGES;
427 $options[$key] = apply_filters( 'mwai_languages', $options[$key] );
428 }
429 }
430 $options['shortcode_chat_default_params'] = MWAI_CHATBOT_PARAMS;
431 $options['chatbot_defaults'] = MWAI_CHATBOT_DEFAULT_PARAMS;
432 $options['default_limits'] = MWAI_LIMITS;
433 $options['openai_models'] = MWAI_OPENAI_MODELS;
434 return $options;
435 }
436
437 // Validate and keep the options clean and logical.
438 function sanitize_options() {
439 $options = $this->get_all_options();
440 $needs_update = false;
441
442 // We can sanitize our future options here, let's always remember it.
443 // Now, it is empty...
444
445 if ( $needs_update ) {
446 update_option( $this->option_name, $options, false );
447 }
448 return $options;
449 }
450
451 function update_options( $options ) {
452 if ( !update_option( $this->option_name, $options, false ) ) {
453 return false;
454 }
455 $options = $this->sanitize_options();
456 return $options;
457 }
458
459 function update_option( $option, $value ) {
460 $options = $this->get_all_options();
461 $options[$option] = $value;
462 return $this->update_options( $options );
463 }
464
465 function get_option( $option, $default = null ) {
466 $options = $this->get_all_options();
467 return $options[$option] ?? $default;
468 }
469 #endregion
470 }
471
472 ?>