Changeset 3428474
- Timestamp:
- 12/28/2025 05:25:52 AM (2 months ago)
- Location:
- bitbloom-chatbot-for-chatkit
- Files:
-
- 6 edited
- 1 copied
-
tags/1.2.1 (copied) (copied from bitbloom-chatbot-for-chatkit/trunk)
-
tags/1.2.1/bitbloom-chatbot-for-chatkit.php (modified) (21 diffs)
-
tags/1.2.1/readme.txt (modified) (6 diffs)
-
tags/1.2.1/uninstall.php (modified) (1 diff)
-
trunk/bitbloom-chatbot-for-chatkit.php (modified) (21 diffs)
-
trunk/readme.txt (modified) (6 diffs)
-
trunk/uninstall.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
bitbloom-chatbot-for-chatkit/tags/1.2.1/bitbloom-chatbot-for-chatkit.php
r3396302 r3428474 6 6 * Author: BitBloom 7 7 * Author URI: https://github.com/BitBloom-HQ 8 * Version: 1.2. 08 * Version: 1.2.1 9 9 * License: GPLv2 or later 10 10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 11 * Requires at least: 6.2 12 * Tested up to: 6. 812 * Tested up to: 6.9 13 13 * Requires PHP: 7.4 14 14 * Text Domain: bitbloom-chatbot-for-chatkit … … 22 22 class BitBloom_Chatbot_for_Chatkit { 23 23 const OPT = 'bitbloom-chatbot-for-chatkit_options'; 24 const VER = '1.2.1'; 24 25 25 26 public function __construct() { … … 111 112 $launcher_fg = $hex($launcher_in['fg'] ?? '#FFFFFF', '#FFFFFF'); 112 113 114 $rate_limit = isset($in['rate_limit_per_hour']) ? absint($in['rate_limit_per_hour']) : 20; 115 if ($rate_limit < 1) { $rate_limit = 1; } 116 if ($rate_limit > 99999) { $rate_limit = 99999; } 117 118 113 119 return [ 114 120 'workflow_id' => sanitize_text_field($in['workflow_id'] ?? ''), … … 116 122 'greeting' => sanitize_text_field($in['greeting'] ?? ''), 117 123 'auto_inject' => !empty($in['auto_inject']) ? '1' : '0', 124 'rate_limit_per_hour' => $rate_limit, 118 125 'theme' => [ 119 126 'color_scheme' => $color_scheme, … … 171 178 <td> 172 179 <input id="bboaa_workflow_id" type="text" class="regular-text" 173 name="<?php echo self::OPT; ?>[workflow_id]"180 name="<?php echo esc_attr( self::OPT ); ?>[workflow_id]" 174 181 value="<?php echo esc_attr($o['workflow_id'] ?? ''); ?>" 175 182 placeholder="wf_xxx..." /> … … 180 187 <td> 181 188 <input id="bboaa_domain_pk" type="text" class="regular-text" 182 name="<?php echo self::OPT; ?>[domain_pk]"189 name="<?php echo esc_attr( self::OPT ); ?>[domain_pk]" 183 190 value="<?php echo esc_attr($o['domain_pk'] ?? ''); ?>" 184 191 placeholder="pk-live-..." /> … … 189 196 <td> 190 197 <input id="bboaa_greeting" type="text" class="regular-text" 191 name="<?php echo self::OPT; ?>[greeting]"198 name="<?php echo esc_attr( self::OPT ); ?>[greeting]" 192 199 value="<?php echo esc_attr($o['greeting'] ?? ''); ?>" 193 200 placeholder="What can I help with today?" /> … … 197 204 198 205 <tr> 206 <th scope="row"> 207 <label for="bboaa_rate_limit_per_hour">Rate limit (per IP / hour)</label> 208 </th> 209 <td> 210 <input 211 id="bboaa_rate_limit_per_hour" 212 type="number" 213 class="small-text" 214 name="<?php echo esc_attr( self::OPT ); ?>[rate_limit_per_hour]" 215 value="<?php echo esc_attr((int)($o['rate_limit_per_hour'] ?? 20)); ?>" 216 min="1" 217 max="99999" 218 step="1" 219 /> 220 <p class="description"> 221 Limits how many ChatKit session requests a single IP can make per hour. Default: 20. 222 </p> 223 </td> 224 </tr> 225 226 227 <tr> 199 228 <th scope="row">Theme</th> 200 229 <td> … … 204 233 <p><strong>Color scheme</strong></p> 205 234 <label style="margin-right:16px;"> 206 <input type="radio" name="<?php echo self::OPT; ?>[theme][color_scheme]" value="light" <?php checked($color_scheme==='light'); ?>> Light235 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][color_scheme]" value="light" <?php checked($color_scheme==='light'); ?>> Light 207 236 </label> 208 237 <label> 209 <input type="radio" name="<?php echo self::OPT; ?>[theme][color_scheme]" value="dark" <?php checked($color_scheme==='dark'); ?>> Dark238 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][color_scheme]" value="dark" <?php checked($color_scheme==='dark'); ?>> Dark 210 239 </label> 211 240 … … 214 243 <?php foreach ($accents as $hex => $label): ?> 215 244 <label style="display:flex; align-items:center; gap:6px; cursor:pointer;"> 216 <input type="radio" name="<?php echo self::OPT; ?>[theme][accent]" value="<?php echo esc_attr($hex); ?>" <?php checked($accent===$hex); ?>>245 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][accent]" value="<?php echo esc_attr($hex); ?>" <?php checked($accent===$hex); ?>> 217 246 <span style="display:inline-block; width:18px; height:18px; border-radius:50%; background:<?php echo esc_attr($hex); ?>; border:1px solid rgba(0,0,0,.12);"></span> 218 247 <span><?php echo esc_html($label); ?></span> … … 223 252 <p style="margin-top:14px;"><strong>Corner radius</strong></p> 224 253 <label style="margin-right:16px;"> 225 <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="pill" <?php checked($radius==='pill'); ?>> Pill254 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="pill" <?php checked($radius==='pill'); ?>> Pill 226 255 </label> 227 256 <label style="margin-right:16px;"> 228 <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="round" <?php checked($radius==='round'); ?>> Round257 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="round" <?php checked($radius==='round'); ?>> Round 229 258 </label> 230 259 <label style="margin-right:16px;"> 231 <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="soft" <?php checked($radius==='soft'); ?>> Soft260 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="soft" <?php checked($radius==='soft'); ?>> Soft 232 261 </label> 233 262 <label> 234 <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="sharp" <?php checked($radius==='sharp'); ?>> Sharp263 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="sharp" <?php checked($radius==='sharp'); ?>> Sharp 235 264 </label> 236 265 … … 238 267 <p style="margin-top:14px;"><strong>Density</strong></p> 239 268 <label style="margin-right:16px;"> 240 <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="compact" <?php checked($density==='compact'); ?>> Compact269 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="compact" <?php checked($density==='compact'); ?>> Compact 241 270 </label> 242 271 <label style="margin-right:16px;"> 243 <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="normal" <?php checked($density==='normal'); ?>> Normal272 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="normal" <?php checked($density==='normal'); ?>> Normal 244 273 </label> 245 274 <label> 246 <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="spacious" <?php checked($density==='spacious'); ?>> Spacious275 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="spacious" <?php checked($density==='spacious'); ?>> Spacious 247 276 </label> 248 277 249 278 250 279 <p style="margin-top:14px;"><strong>Font</strong></p> 251 <select name="<?php echo self::OPT; ?>[theme][font]">280 <select name="<?php echo esc_attr( self::OPT ); ?>[theme][font]"> 252 281 <?php foreach ($fonts as $token => $label): ?> 253 282 <option value="<?php echo esc_attr($token); ?>" <?php selected($font===$token); ?>> … … 289 318 <label style="display:block; margin-bottom:6px;">Text</label> 290 319 <input type="text" class="regular-text" 291 name="<?php echo self::OPT; ?>[launcher][text]"320 name="<?php echo esc_attr( self::OPT ); ?>[launcher][text]" 292 321 value="<?php echo esc_attr($l_text); ?>" 293 322 placeholder="Chat with our AI" /> … … 297 326 <label style="display:block; margin-bottom:6px;">Background</label> 298 327 <input type="color" value="<?php echo esc_attr($l_bg); ?>" 299 name="<?php echo self::OPT; ?>[launcher][bg]" />328 name="<?php echo esc_attr( self::OPT ); ?>[launcher][bg]" /> 300 329 </span> 301 330 <span> 302 331 <label style="display:block; margin-bottom:6px;">Text color</label> 303 332 <input type="color" value="<?php echo esc_attr($l_fg); ?>" 304 name="<?php echo self::OPT; ?>[launcher][fg]" />333 name="<?php echo esc_attr( self::OPT ); ?>[launcher][fg]" /> 305 334 </span> 306 335 </p> … … 320 349 <td> 321 350 <label> 322 <input type="checkbox" name="<?php echo self::OPT; ?>[auto_inject]" value="1" <?php checked(($o['auto_inject'] ?? '') === '1'); ?> />351 <input type="checkbox" name="<?php echo esc_attr( self::OPT ); ?>[auto_inject]" value="1" <?php checked(($o['auto_inject'] ?? '') === '1'); ?> /> 323 352 Enable floating chat launcher globally 324 353 </label> … … 349 378 'https://cdn.platform.openai.com/deployments/chatkit/chatkit.js', 350 379 [], 351 null,380 self::VER, 352 381 true 353 382 ); … … 357 386 plugins_url('assets/bitbloom-agent.js', __FILE__), 358 387 ['openai-chatkit'], 359 '1.3.1',388 self::VER, 360 389 true 361 390 ); … … 474 503 $o = get_option(self::OPT, []); 475 504 if (($o['auto_inject'] ?? '') === '1') { 505 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- embed_markup escapes dynamic values; output is controlled markup. 476 506 echo $this->embed_markup(); 507 477 508 $printed = true; 478 509 } … … 532 563 <?php 533 564 $title_msg = __('Configure workflow & domain key in Settings → BitBloom Chatbot for Chatkit', 'bitbloom-chatbot-for-chatkit'); 534 $btn_attrs = sprintf('title="%s"%s', 535 esc_attr($title_msg), 536 $ready ? '' : ' disabled' 537 ); 538 ?> 539 <button id="bitbloom-agent-launcher" aria-haspopup="dialog" <?php echo $btn_attrs; ?>> 540 <?php echo esc_html($l_text); ?> 565 ?> 566 <button 567 id="bitbloom-agent-launcher" 568 aria-haspopup="dialog" 569 title="<?php echo esc_attr( $title_msg ); ?>" 570 <?php disabled( $ready, false ); ?> 571 > 572 <?php echo esc_html( $l_text ); ?> 541 573 </button> 542 <?php 574 <?php 575 543 576 return ob_get_clean(); 544 577 } … … 564 597 return new \WP_Error('forbidden', 'Invalid REST nonce', ['status' => 403]); 565 598 } 566 // Basic rate-limit per IP (20/hour): 567 $ip = $_SERVER['REMOTE_ADDR'] ?? 'na'; 568 $key = 'bitbchfo_rl_' . md5($ip); 599 600 // Basic rate-limit per IP (configurable, per hour) 601 $o = get_option(self::OPT, []); 602 $limit = absint($o['rate_limit_per_hour'] ?? 20); 603 if ($limit < 1) { $limit = 1; } 604 if ($limit > 99999) { $limit = 99999; } 605 606 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- $_SERVER value is immediately unslashed and sanitized below. 607 $ip_raw = isset( $_SERVER['REMOTE_ADDR'] ) ? wp_unslash( $_SERVER['REMOTE_ADDR'] ) : ''; 608 $ip = sanitize_text_field( $ip_raw ); 609 if ( $ip === '' ) { 610 $ip = 'na'; 611 } 612 $key = 'bitbchfo_rl_' . md5( $ip ); 613 569 614 $count = (int) get_transient($key); 570 if ($count > 20) { 615 616 if ($count >= $limit) { 571 617 return new \WP_Error('rate_limited', 'Too Many Requests', ['status' => 429]); 572 618 } 619 573 620 set_transient($key, $count + 1, HOUR_IN_SECONDS); 574 621 return true; 622 575 623 }, 576 624 ]); … … 590 638 // Create a stable-but-anonymous "user" string (no PII) 591 639 $site_salt = wp_salt('auth'); 592 $ua_raw = isset($_SERVER['HTTP_USER_AGENT']) ? wp_unslash($_SERVER['HTTP_USER_AGENT']) : ''; 593 $ua_clean = sanitize_text_field($ua_raw); 640 $ua_clean = isset( $_SERVER['HTTP_USER_AGENT'] ) 641 ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) 642 : ''; 643 594 644 $user = substr(hash('sha256', $site_salt . '|' . $ua_clean), 0, 64); 595 645 -
bitbloom-chatbot-for-chatkit/tags/1.2.1/readme.txt
r3396311 r3428474 3 3 Tags: openai, chatkit, chat, embed, ai 4 4 Requires at least: 6.2 5 Tested up to: 6. 85 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.2. 07 Stable tag: 1.2.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 21 21 - Theme controls: color scheme, accent, radius, density, font. 22 22 - Secure: session created on the server; REST calls nonce-protected. 23 - Rate limiting: configurable per-IP hourly limit for session requests (default 20/hour). 23 24 - Privacy-friendly: user ID is a salted hash (no IP stored or sent by the plugin). 24 25 … … 30 31 == Installation == 31 32 For a full video walkthrough of the installation and setup process: 32 https://youtu.be/ PstZVUaumw833 https://youtu.be/Kd0WxODDdYc 33 34 34 35 1. Download the ZIP and upload it via **Plugins → Add New → Upload Plugin**, … … 63 64 64 65 [bitbloom_chatbot_for_chatkit] 66 67 8. (Optional) Adjust **Rate limit** (per IP / hour). Default is 20/hour. 68 65 69 66 70 … … 106 110 Yes. You can change the color scheme (light/dark), accent color, radius, density, and font. 107 111 112 = Is there a usage/rate limit in the plugin? = 113 Yes. By default, the plugin limits session requests to 20 per hour per IP. 114 You can change this in the plugin settings (Rate limit per IP / hour) to any value between 1 and 99999. 115 108 116 = The chat window shows an error when loading. What causes this? = 109 117 Usually one of the following: … … 140 148 141 149 == Changelog == 150 = 1.2.1 = 151 * Added an admin setting to configure the per-IP hourly rate limit for session requests (default: 20/hour). 142 152 = 1.2.0 = 143 153 * Initial public release under the name “BitBloom ChatKit Embed”. 144 154 145 155 == Upgrade Notice == 156 = 1.2.1 = 157 Adds a configurable rate limit setting (default 20/hour). 146 158 = 1.2.0 = 147 159 First stable release. -
bitbloom-chatbot-for-chatkit/tags/1.2.1/uninstall.php
r3396302 r3428474 1 1 <?php 2 if ( ! defined( 'WP_UNINSTALL_PLUGIN') ) {2 if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { 3 3 exit; 4 4 } 5 5 6 $option = 'bitbloom-chatbot-for-chatkit_options'; 6 (function () { 7 $option_name = 'bitbloom-chatbot-for-chatkit_options'; 7 8 8 // Delete options (single site) 9 delete_option($option);9 // Delete options (single site). 10 delete_option( $option_name ); 10 11 11 // Delete network (multisite) options if present 12 if ( is_multisite() ) {13 delete_site_option($option);14 }12 // Delete network (multisite) options if present. 13 if ( is_multisite() ) { 14 delete_site_option( $option_name ); 15 } 15 16 16 // Clean up transient-based rate limits: bba_rl_* 17 // This removes both the transient and its timeout rows. 18 global $wpdb; 19 $like1 = $wpdb->esc_like('_transient_bitbchfo_rl_') . '%'; 20 $like2 = $wpdb->esc_like('_transient_timeout_bitbchfo_rl_') . '%'; 21 $wpdb->query( $wpdb->prepare( 22 "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", 23 $like1, $like2 24 ) ); 17 // Clean up transient-based rate limits: bitbchfo_rl_* 18 // This removes both the transient and its timeout rows. 19 global $wpdb; 25 20 21 $like1 = $wpdb->esc_like( '_transient_bitbchfo_rl_' ) . '%'; 22 $like2 = $wpdb->esc_like( '_transient_timeout_bitbchfo_rl_' ) . '%'; 23 24 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- uninstall cleanup for transients. 25 $wpdb->query( 26 $wpdb->prepare( 27 "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", 28 $like1, 29 $like2 30 ) 31 ); 32 })(); -
bitbloom-chatbot-for-chatkit/trunk/bitbloom-chatbot-for-chatkit.php
r3396302 r3428474 6 6 * Author: BitBloom 7 7 * Author URI: https://github.com/BitBloom-HQ 8 * Version: 1.2. 08 * Version: 1.2.1 9 9 * License: GPLv2 or later 10 10 * License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 11 * Requires at least: 6.2 12 * Tested up to: 6. 812 * Tested up to: 6.9 13 13 * Requires PHP: 7.4 14 14 * Text Domain: bitbloom-chatbot-for-chatkit … … 22 22 class BitBloom_Chatbot_for_Chatkit { 23 23 const OPT = 'bitbloom-chatbot-for-chatkit_options'; 24 const VER = '1.2.1'; 24 25 25 26 public function __construct() { … … 111 112 $launcher_fg = $hex($launcher_in['fg'] ?? '#FFFFFF', '#FFFFFF'); 112 113 114 $rate_limit = isset($in['rate_limit_per_hour']) ? absint($in['rate_limit_per_hour']) : 20; 115 if ($rate_limit < 1) { $rate_limit = 1; } 116 if ($rate_limit > 99999) { $rate_limit = 99999; } 117 118 113 119 return [ 114 120 'workflow_id' => sanitize_text_field($in['workflow_id'] ?? ''), … … 116 122 'greeting' => sanitize_text_field($in['greeting'] ?? ''), 117 123 'auto_inject' => !empty($in['auto_inject']) ? '1' : '0', 124 'rate_limit_per_hour' => $rate_limit, 118 125 'theme' => [ 119 126 'color_scheme' => $color_scheme, … … 171 178 <td> 172 179 <input id="bboaa_workflow_id" type="text" class="regular-text" 173 name="<?php echo self::OPT; ?>[workflow_id]"180 name="<?php echo esc_attr( self::OPT ); ?>[workflow_id]" 174 181 value="<?php echo esc_attr($o['workflow_id'] ?? ''); ?>" 175 182 placeholder="wf_xxx..." /> … … 180 187 <td> 181 188 <input id="bboaa_domain_pk" type="text" class="regular-text" 182 name="<?php echo self::OPT; ?>[domain_pk]"189 name="<?php echo esc_attr( self::OPT ); ?>[domain_pk]" 183 190 value="<?php echo esc_attr($o['domain_pk'] ?? ''); ?>" 184 191 placeholder="pk-live-..." /> … … 189 196 <td> 190 197 <input id="bboaa_greeting" type="text" class="regular-text" 191 name="<?php echo self::OPT; ?>[greeting]"198 name="<?php echo esc_attr( self::OPT ); ?>[greeting]" 192 199 value="<?php echo esc_attr($o['greeting'] ?? ''); ?>" 193 200 placeholder="What can I help with today?" /> … … 197 204 198 205 <tr> 206 <th scope="row"> 207 <label for="bboaa_rate_limit_per_hour">Rate limit (per IP / hour)</label> 208 </th> 209 <td> 210 <input 211 id="bboaa_rate_limit_per_hour" 212 type="number" 213 class="small-text" 214 name="<?php echo esc_attr( self::OPT ); ?>[rate_limit_per_hour]" 215 value="<?php echo esc_attr((int)($o['rate_limit_per_hour'] ?? 20)); ?>" 216 min="1" 217 max="99999" 218 step="1" 219 /> 220 <p class="description"> 221 Limits how many ChatKit session requests a single IP can make per hour. Default: 20. 222 </p> 223 </td> 224 </tr> 225 226 227 <tr> 199 228 <th scope="row">Theme</th> 200 229 <td> … … 204 233 <p><strong>Color scheme</strong></p> 205 234 <label style="margin-right:16px;"> 206 <input type="radio" name="<?php echo self::OPT; ?>[theme][color_scheme]" value="light" <?php checked($color_scheme==='light'); ?>> Light235 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][color_scheme]" value="light" <?php checked($color_scheme==='light'); ?>> Light 207 236 </label> 208 237 <label> 209 <input type="radio" name="<?php echo self::OPT; ?>[theme][color_scheme]" value="dark" <?php checked($color_scheme==='dark'); ?>> Dark238 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][color_scheme]" value="dark" <?php checked($color_scheme==='dark'); ?>> Dark 210 239 </label> 211 240 … … 214 243 <?php foreach ($accents as $hex => $label): ?> 215 244 <label style="display:flex; align-items:center; gap:6px; cursor:pointer;"> 216 <input type="radio" name="<?php echo self::OPT; ?>[theme][accent]" value="<?php echo esc_attr($hex); ?>" <?php checked($accent===$hex); ?>>245 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][accent]" value="<?php echo esc_attr($hex); ?>" <?php checked($accent===$hex); ?>> 217 246 <span style="display:inline-block; width:18px; height:18px; border-radius:50%; background:<?php echo esc_attr($hex); ?>; border:1px solid rgba(0,0,0,.12);"></span> 218 247 <span><?php echo esc_html($label); ?></span> … … 223 252 <p style="margin-top:14px;"><strong>Corner radius</strong></p> 224 253 <label style="margin-right:16px;"> 225 <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="pill" <?php checked($radius==='pill'); ?>> Pill254 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="pill" <?php checked($radius==='pill'); ?>> Pill 226 255 </label> 227 256 <label style="margin-right:16px;"> 228 <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="round" <?php checked($radius==='round'); ?>> Round257 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="round" <?php checked($radius==='round'); ?>> Round 229 258 </label> 230 259 <label style="margin-right:16px;"> 231 <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="soft" <?php checked($radius==='soft'); ?>> Soft260 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="soft" <?php checked($radius==='soft'); ?>> Soft 232 261 </label> 233 262 <label> 234 <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="sharp" <?php checked($radius==='sharp'); ?>> Sharp263 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="sharp" <?php checked($radius==='sharp'); ?>> Sharp 235 264 </label> 236 265 … … 238 267 <p style="margin-top:14px;"><strong>Density</strong></p> 239 268 <label style="margin-right:16px;"> 240 <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="compact" <?php checked($density==='compact'); ?>> Compact269 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="compact" <?php checked($density==='compact'); ?>> Compact 241 270 </label> 242 271 <label style="margin-right:16px;"> 243 <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="normal" <?php checked($density==='normal'); ?>> Normal272 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="normal" <?php checked($density==='normal'); ?>> Normal 244 273 </label> 245 274 <label> 246 <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="spacious" <?php checked($density==='spacious'); ?>> Spacious275 <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="spacious" <?php checked($density==='spacious'); ?>> Spacious 247 276 </label> 248 277 249 278 250 279 <p style="margin-top:14px;"><strong>Font</strong></p> 251 <select name="<?php echo self::OPT; ?>[theme][font]">280 <select name="<?php echo esc_attr( self::OPT ); ?>[theme][font]"> 252 281 <?php foreach ($fonts as $token => $label): ?> 253 282 <option value="<?php echo esc_attr($token); ?>" <?php selected($font===$token); ?>> … … 289 318 <label style="display:block; margin-bottom:6px;">Text</label> 290 319 <input type="text" class="regular-text" 291 name="<?php echo self::OPT; ?>[launcher][text]"320 name="<?php echo esc_attr( self::OPT ); ?>[launcher][text]" 292 321 value="<?php echo esc_attr($l_text); ?>" 293 322 placeholder="Chat with our AI" /> … … 297 326 <label style="display:block; margin-bottom:6px;">Background</label> 298 327 <input type="color" value="<?php echo esc_attr($l_bg); ?>" 299 name="<?php echo self::OPT; ?>[launcher][bg]" />328 name="<?php echo esc_attr( self::OPT ); ?>[launcher][bg]" /> 300 329 </span> 301 330 <span> 302 331 <label style="display:block; margin-bottom:6px;">Text color</label> 303 332 <input type="color" value="<?php echo esc_attr($l_fg); ?>" 304 name="<?php echo self::OPT; ?>[launcher][fg]" />333 name="<?php echo esc_attr( self::OPT ); ?>[launcher][fg]" /> 305 334 </span> 306 335 </p> … … 320 349 <td> 321 350 <label> 322 <input type="checkbox" name="<?php echo self::OPT; ?>[auto_inject]" value="1" <?php checked(($o['auto_inject'] ?? '') === '1'); ?> />351 <input type="checkbox" name="<?php echo esc_attr( self::OPT ); ?>[auto_inject]" value="1" <?php checked(($o['auto_inject'] ?? '') === '1'); ?> /> 323 352 Enable floating chat launcher globally 324 353 </label> … … 349 378 'https://cdn.platform.openai.com/deployments/chatkit/chatkit.js', 350 379 [], 351 null,380 self::VER, 352 381 true 353 382 ); … … 357 386 plugins_url('assets/bitbloom-agent.js', __FILE__), 358 387 ['openai-chatkit'], 359 '1.3.1',388 self::VER, 360 389 true 361 390 ); … … 474 503 $o = get_option(self::OPT, []); 475 504 if (($o['auto_inject'] ?? '') === '1') { 505 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- embed_markup escapes dynamic values; output is controlled markup. 476 506 echo $this->embed_markup(); 507 477 508 $printed = true; 478 509 } … … 532 563 <?php 533 564 $title_msg = __('Configure workflow & domain key in Settings → BitBloom Chatbot for Chatkit', 'bitbloom-chatbot-for-chatkit'); 534 $btn_attrs = sprintf('title="%s"%s', 535 esc_attr($title_msg), 536 $ready ? '' : ' disabled' 537 ); 538 ?> 539 <button id="bitbloom-agent-launcher" aria-haspopup="dialog" <?php echo $btn_attrs; ?>> 540 <?php echo esc_html($l_text); ?> 565 ?> 566 <button 567 id="bitbloom-agent-launcher" 568 aria-haspopup="dialog" 569 title="<?php echo esc_attr( $title_msg ); ?>" 570 <?php disabled( $ready, false ); ?> 571 > 572 <?php echo esc_html( $l_text ); ?> 541 573 </button> 542 <?php 574 <?php 575 543 576 return ob_get_clean(); 544 577 } … … 564 597 return new \WP_Error('forbidden', 'Invalid REST nonce', ['status' => 403]); 565 598 } 566 // Basic rate-limit per IP (20/hour): 567 $ip = $_SERVER['REMOTE_ADDR'] ?? 'na'; 568 $key = 'bitbchfo_rl_' . md5($ip); 599 600 // Basic rate-limit per IP (configurable, per hour) 601 $o = get_option(self::OPT, []); 602 $limit = absint($o['rate_limit_per_hour'] ?? 20); 603 if ($limit < 1) { $limit = 1; } 604 if ($limit > 99999) { $limit = 99999; } 605 606 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- $_SERVER value is immediately unslashed and sanitized below. 607 $ip_raw = isset( $_SERVER['REMOTE_ADDR'] ) ? wp_unslash( $_SERVER['REMOTE_ADDR'] ) : ''; 608 $ip = sanitize_text_field( $ip_raw ); 609 if ( $ip === '' ) { 610 $ip = 'na'; 611 } 612 $key = 'bitbchfo_rl_' . md5( $ip ); 613 569 614 $count = (int) get_transient($key); 570 if ($count > 20) { 615 616 if ($count >= $limit) { 571 617 return new \WP_Error('rate_limited', 'Too Many Requests', ['status' => 429]); 572 618 } 619 573 620 set_transient($key, $count + 1, HOUR_IN_SECONDS); 574 621 return true; 622 575 623 }, 576 624 ]); … … 590 638 // Create a stable-but-anonymous "user" string (no PII) 591 639 $site_salt = wp_salt('auth'); 592 $ua_raw = isset($_SERVER['HTTP_USER_AGENT']) ? wp_unslash($_SERVER['HTTP_USER_AGENT']) : ''; 593 $ua_clean = sanitize_text_field($ua_raw); 640 $ua_clean = isset( $_SERVER['HTTP_USER_AGENT'] ) 641 ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) 642 : ''; 643 594 644 $user = substr(hash('sha256', $site_salt . '|' . $ua_clean), 0, 64); 595 645 -
bitbloom-chatbot-for-chatkit/trunk/readme.txt
r3396311 r3428474 3 3 Tags: openai, chatkit, chat, embed, ai 4 4 Requires at least: 6.2 5 Tested up to: 6. 85 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.2. 07 Stable tag: 1.2.1 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 21 21 - Theme controls: color scheme, accent, radius, density, font. 22 22 - Secure: session created on the server; REST calls nonce-protected. 23 - Rate limiting: configurable per-IP hourly limit for session requests (default 20/hour). 23 24 - Privacy-friendly: user ID is a salted hash (no IP stored or sent by the plugin). 24 25 … … 30 31 == Installation == 31 32 For a full video walkthrough of the installation and setup process: 32 https://youtu.be/ PstZVUaumw833 https://youtu.be/Kd0WxODDdYc 33 34 34 35 1. Download the ZIP and upload it via **Plugins → Add New → Upload Plugin**, … … 63 64 64 65 [bitbloom_chatbot_for_chatkit] 66 67 8. (Optional) Adjust **Rate limit** (per IP / hour). Default is 20/hour. 68 65 69 66 70 … … 106 110 Yes. You can change the color scheme (light/dark), accent color, radius, density, and font. 107 111 112 = Is there a usage/rate limit in the plugin? = 113 Yes. By default, the plugin limits session requests to 20 per hour per IP. 114 You can change this in the plugin settings (Rate limit per IP / hour) to any value between 1 and 99999. 115 108 116 = The chat window shows an error when loading. What causes this? = 109 117 Usually one of the following: … … 140 148 141 149 == Changelog == 150 = 1.2.1 = 151 * Added an admin setting to configure the per-IP hourly rate limit for session requests (default: 20/hour). 142 152 = 1.2.0 = 143 153 * Initial public release under the name “BitBloom ChatKit Embed”. 144 154 145 155 == Upgrade Notice == 156 = 1.2.1 = 157 Adds a configurable rate limit setting (default 20/hour). 146 158 = 1.2.0 = 147 159 First stable release. -
bitbloom-chatbot-for-chatkit/trunk/uninstall.php
r3396302 r3428474 1 1 <?php 2 if ( ! defined( 'WP_UNINSTALL_PLUGIN') ) {2 if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { 3 3 exit; 4 4 } 5 5 6 $option = 'bitbloom-chatbot-for-chatkit_options'; 6 (function () { 7 $option_name = 'bitbloom-chatbot-for-chatkit_options'; 7 8 8 // Delete options (single site) 9 delete_option($option);9 // Delete options (single site). 10 delete_option( $option_name ); 10 11 11 // Delete network (multisite) options if present 12 if ( is_multisite() ) {13 delete_site_option($option);14 }12 // Delete network (multisite) options if present. 13 if ( is_multisite() ) { 14 delete_site_option( $option_name ); 15 } 15 16 16 // Clean up transient-based rate limits: bba_rl_* 17 // This removes both the transient and its timeout rows. 18 global $wpdb; 19 $like1 = $wpdb->esc_like('_transient_bitbchfo_rl_') . '%'; 20 $like2 = $wpdb->esc_like('_transient_timeout_bitbchfo_rl_') . '%'; 21 $wpdb->query( $wpdb->prepare( 22 "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", 23 $like1, $like2 24 ) ); 17 // Clean up transient-based rate limits: bitbchfo_rl_* 18 // This removes both the transient and its timeout rows. 19 global $wpdb; 25 20 21 $like1 = $wpdb->esc_like( '_transient_bitbchfo_rl_' ) . '%'; 22 $like2 = $wpdb->esc_like( '_transient_timeout_bitbchfo_rl_' ) . '%'; 23 24 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- uninstall cleanup for transients. 25 $wpdb->query( 26 $wpdb->prepare( 27 "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", 28 $like1, 29 $like2 30 ) 31 ); 32 })();
Note: See TracChangeset
for help on using the changeset viewer.