Plugin Directory

Changeset 3428474


Ignore:
Timestamp:
12/28/2025 05:25:52 AM (2 months ago)
Author:
ishchai
Message:

Release 1.2.1: configurable rate limit and WP 6.9 compatibility (Plugin Check clean)

Location:
bitbloom-chatbot-for-chatkit
Files:
6 edited
1 copied

Legend:

Unmodified
Added
Removed
  • bitbloom-chatbot-for-chatkit/tags/1.2.1/bitbloom-chatbot-for-chatkit.php

    r3396302 r3428474  
    66 * Author:            BitBloom
    77 * Author URI:        https://github.com/BitBloom-HQ
    8  * Version:           1.2.0
     8 * Version:           1.2.1
    99 * License:           GPLv2 or later
    1010 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
    1111 * Requires at least: 6.2
    12  * Tested up to:      6.8
     12 * Tested up to:      6.9
    1313 * Requires PHP:      7.4
    1414 * Text Domain:       bitbloom-chatbot-for-chatkit
     
    2222class BitBloom_Chatbot_for_Chatkit {
    2323  const OPT = 'bitbloom-chatbot-for-chatkit_options';
     24  const VER = '1.2.1';
    2425
    2526  public function __construct() {
     
    111112      $launcher_fg   = $hex($launcher_in['fg'] ?? '#FFFFFF', '#FFFFFF');
    112113
     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
    113119      return [
    114120        'workflow_id' => sanitize_text_field($in['workflow_id'] ?? ''),
     
    116122        'greeting'    => sanitize_text_field($in['greeting']    ?? ''),
    117123        'auto_inject' => !empty($in['auto_inject']) ? '1' : '0',
     124        'rate_limit_per_hour' => $rate_limit,
    118125        'theme'       => [
    119126          'color_scheme' => $color_scheme,
     
    171178          <td>
    172179            <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]"
    174181                   value="<?php echo esc_attr($o['workflow_id'] ?? ''); ?>"
    175182                   placeholder="wf_xxx..." />
     
    180187          <td>
    181188            <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]"
    183190                   value="<?php echo esc_attr($o['domain_pk'] ?? ''); ?>"
    184191                   placeholder="pk-live-..." />
     
    189196          <td>
    190197            <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]"
    192199                   value="<?php echo esc_attr($o['greeting'] ?? ''); ?>"
    193200                   placeholder="What can I help with today?" />
     
    197204
    198205        <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>
    199228          <th scope="row">Theme</th>
    200229          <td>
     
    204233              <p><strong>Color scheme</strong></p>
    205234              <label style="margin-right:16px;">
    206                 <input type="radio" name="<?php echo self::OPT; ?>[theme][color_scheme]" value="light" <?php checked($color_scheme==='light'); ?>> Light
     235                <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][color_scheme]" value="light" <?php checked($color_scheme==='light'); ?>> Light
    207236              </label>
    208237              <label>
    209                 <input type="radio" name="<?php echo self::OPT; ?>[theme][color_scheme]" value="dark" <?php checked($color_scheme==='dark'); ?>> Dark
     238                <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][color_scheme]" value="dark" <?php checked($color_scheme==='dark'); ?>> Dark
    210239              </label>
    211240
     
    214243                <?php foreach ($accents as $hex => $label): ?>
    215244                  <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); ?>>
    217246                    <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>
    218247                    <span><?php echo esc_html($label); ?></span>
     
    223252            <p style="margin-top:14px;"><strong>Corner radius</strong></p>
    224253            <label style="margin-right:16px;">
    225             <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="pill"  <?php checked($radius==='pill');  ?>> Pill
     254            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="pill"  <?php checked($radius==='pill');  ?>> Pill
    226255            </label>
    227256            <label style="margin-right:16px;">
    228             <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="round" <?php checked($radius==='round'); ?>> Round
     257            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="round" <?php checked($radius==='round'); ?>> Round
    229258            </label>
    230259            <label style="margin-right:16px;">
    231             <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="soft"  <?php checked($radius==='soft');  ?>> Soft
     260            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="soft"  <?php checked($radius==='soft');  ?>> Soft
    232261            </label>
    233262            <label>
    234             <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="sharp" <?php checked($radius==='sharp'); ?>> Sharp
     263            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="sharp" <?php checked($radius==='sharp'); ?>> Sharp
    235264            </label>
    236265
     
    238267            <p style="margin-top:14px;"><strong>Density</strong></p>
    239268            <label style="margin-right:16px;">
    240             <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="compact"  <?php checked($density==='compact');  ?>> Compact
     269            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="compact"  <?php checked($density==='compact');  ?>> Compact
    241270            </label>
    242271            <label style="margin-right:16px;">
    243             <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="normal"   <?php checked($density==='normal');   ?>> Normal
     272            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="normal"   <?php checked($density==='normal');   ?>> Normal
    244273            </label>
    245274            <label>
    246             <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="spacious" <?php checked($density==='spacious'); ?>> Spacious
     275            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="spacious" <?php checked($density==='spacious'); ?>> Spacious
    247276            </label>
    248277
    249278
    250279            <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]">
    252281                <?php foreach ($fonts as $token => $label): ?>
    253282                    <option value="<?php echo esc_attr($token); ?>" <?php selected($font===$token); ?>>
     
    289318                <label style="display:block; margin-bottom:6px;">Text</label>
    290319                <input type="text" class="regular-text"
    291                         name="<?php echo self::OPT; ?>[launcher][text]"
     320                        name="<?php echo esc_attr( self::OPT ); ?>[launcher][text]"
    292321                        value="<?php echo esc_attr($l_text); ?>"
    293322                        placeholder="Chat with our AI" />
     
    297326                    <label style="display:block; margin-bottom:6px;">Background</label>
    298327                    <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]" />
    300329                </span>
    301330                <span>
    302331                    <label style="display:block; margin-bottom:6px;">Text color</label>
    303332                    <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]" />
    305334                </span>
    306335                </p>
     
    320349          <td>
    321350            <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'); ?> />
    323352              Enable floating chat launcher globally
    324353            </label>
     
    349378          'https://cdn.platform.openai.com/deployments/chatkit/chatkit.js',
    350379          [],
    351           null,
     380          self::VER,
    352381          true
    353382      );
     
    357386          plugins_url('assets/bitbloom-agent.js', __FILE__),
    358387          ['openai-chatkit'],
    359           '1.3.1',
     388          self::VER,
    360389          true
    361390      );
     
    474503        $o = get_option(self::OPT, []);
    475504        if (($o['auto_inject'] ?? '') === '1') {
     505            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- embed_markup escapes dynamic values; output is controlled markup.
    476506            echo $this->embed_markup();
     507
    477508            $printed = true;
    478509        }
     
    532563      <?php
    533564        $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 ); ?>
    541573        </button>
    542       <?php
     574        <?php
     575
    543576      return ob_get_clean();
    544577    }
     
    564597                    return new \WP_Error('forbidden', 'Invalid REST nonce', ['status' => 403]);
    565598                }
    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
    569614                $count = (int) get_transient($key);
    570                 if ($count > 20) {
     615
     616                if ($count >= $limit) {
    571617                    return new \WP_Error('rate_limited', 'Too Many Requests', ['status' => 429]);
    572618                }
     619
    573620                set_transient($key, $count + 1, HOUR_IN_SECONDS);
    574621                return true;
     622
    575623            },
    576624        ]);
     
    590638        // Create a stable-but-anonymous "user" string (no PII)
    591639        $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
    594644        $user      = substr(hash('sha256', $site_salt . '|' . $ua_clean), 0, 64);
    595645
  • bitbloom-chatbot-for-chatkit/tags/1.2.1/readme.txt

    r3396311 r3428474  
    33Tags: openai, chatkit, chat, embed, ai
    44Requires at least: 6.2
    5 Tested up to: 6.8
     5Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.2.0
     7Stable tag: 1.2.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    2121- Theme controls: color scheme, accent, radius, density, font.
    2222- Secure: session created on the server; REST calls nonce-protected.
     23- Rate limiting: configurable per-IP hourly limit for session requests (default 20/hour).
    2324- Privacy-friendly: user ID is a salted hash (no IP stored or sent by the plugin).
    2425
     
    3031== Installation ==
    3132For a full video walkthrough of the installation and setup process:
    32 https://youtu.be/PstZVUaumw8
     33https://youtu.be/Kd0WxODDdYc
    3334
    34351. Download the ZIP and upload it via **Plugins → Add New → Upload Plugin**,
     
    6364   
    6465   [bitbloom_chatbot_for_chatkit]
     66
     678. (Optional) Adjust **Rate limit** (per IP / hour). Default is 20/hour.
     68
    6569
    6670
     
    106110Yes. You can change the color scheme (light/dark), accent color, radius, density, and font.
    107111
     112= Is there a usage/rate limit in the plugin? =
     113Yes. By default, the plugin limits session requests to 20 per hour per IP.
     114You can change this in the plugin settings (Rate limit per IP / hour) to any value between 1 and 99999.
     115
    108116= The chat window shows an error when loading. What causes this? =
    109117Usually one of the following:
     
    140148
    141149== Changelog ==
     150= 1.2.1 =
     151* Added an admin setting to configure the per-IP hourly rate limit for session requests (default: 20/hour).
    142152= 1.2.0 =
    143153* Initial public release under the name “BitBloom ChatKit Embed”.
    144154
    145155== Upgrade Notice ==
     156= 1.2.1 =
     157Adds a configurable rate limit setting (default 20/hour).
    146158= 1.2.0 =
    147159First stable release.
  • bitbloom-chatbot-for-chatkit/tags/1.2.1/uninstall.php

    r3396302 r3428474  
    11<?php
    2 if ( ! defined('WP_UNINSTALL_PLUGIN') ) {
     2if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    33    exit;
    44}
    55
    6 $option = 'bitbloom-chatbot-for-chatkit_options';
     6(function () {
     7    $option_name = 'bitbloom-chatbot-for-chatkit_options';
    78
    8 // Delete options (single site)
    9 delete_option($option);
     9    // Delete options (single site).
     10    delete_option( $option_name );
    1011
    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    }
    1516
    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;
    2520
     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  
    66 * Author:            BitBloom
    77 * Author URI:        https://github.com/BitBloom-HQ
    8  * Version:           1.2.0
     8 * Version:           1.2.1
    99 * License:           GPLv2 or later
    1010 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
    1111 * Requires at least: 6.2
    12  * Tested up to:      6.8
     12 * Tested up to:      6.9
    1313 * Requires PHP:      7.4
    1414 * Text Domain:       bitbloom-chatbot-for-chatkit
     
    2222class BitBloom_Chatbot_for_Chatkit {
    2323  const OPT = 'bitbloom-chatbot-for-chatkit_options';
     24  const VER = '1.2.1';
    2425
    2526  public function __construct() {
     
    111112      $launcher_fg   = $hex($launcher_in['fg'] ?? '#FFFFFF', '#FFFFFF');
    112113
     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
    113119      return [
    114120        'workflow_id' => sanitize_text_field($in['workflow_id'] ?? ''),
     
    116122        'greeting'    => sanitize_text_field($in['greeting']    ?? ''),
    117123        'auto_inject' => !empty($in['auto_inject']) ? '1' : '0',
     124        'rate_limit_per_hour' => $rate_limit,
    118125        'theme'       => [
    119126          'color_scheme' => $color_scheme,
     
    171178          <td>
    172179            <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]"
    174181                   value="<?php echo esc_attr($o['workflow_id'] ?? ''); ?>"
    175182                   placeholder="wf_xxx..." />
     
    180187          <td>
    181188            <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]"
    183190                   value="<?php echo esc_attr($o['domain_pk'] ?? ''); ?>"
    184191                   placeholder="pk-live-..." />
     
    189196          <td>
    190197            <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]"
    192199                   value="<?php echo esc_attr($o['greeting'] ?? ''); ?>"
    193200                   placeholder="What can I help with today?" />
     
    197204
    198205        <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>
    199228          <th scope="row">Theme</th>
    200229          <td>
     
    204233              <p><strong>Color scheme</strong></p>
    205234              <label style="margin-right:16px;">
    206                 <input type="radio" name="<?php echo self::OPT; ?>[theme][color_scheme]" value="light" <?php checked($color_scheme==='light'); ?>> Light
     235                <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][color_scheme]" value="light" <?php checked($color_scheme==='light'); ?>> Light
    207236              </label>
    208237              <label>
    209                 <input type="radio" name="<?php echo self::OPT; ?>[theme][color_scheme]" value="dark" <?php checked($color_scheme==='dark'); ?>> Dark
     238                <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][color_scheme]" value="dark" <?php checked($color_scheme==='dark'); ?>> Dark
    210239              </label>
    211240
     
    214243                <?php foreach ($accents as $hex => $label): ?>
    215244                  <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); ?>>
    217246                    <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>
    218247                    <span><?php echo esc_html($label); ?></span>
     
    223252            <p style="margin-top:14px;"><strong>Corner radius</strong></p>
    224253            <label style="margin-right:16px;">
    225             <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="pill"  <?php checked($radius==='pill');  ?>> Pill
     254            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="pill"  <?php checked($radius==='pill');  ?>> Pill
    226255            </label>
    227256            <label style="margin-right:16px;">
    228             <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="round" <?php checked($radius==='round'); ?>> Round
     257            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="round" <?php checked($radius==='round'); ?>> Round
    229258            </label>
    230259            <label style="margin-right:16px;">
    231             <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="soft"  <?php checked($radius==='soft');  ?>> Soft
     260            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="soft"  <?php checked($radius==='soft');  ?>> Soft
    232261            </label>
    233262            <label>
    234             <input type="radio" name="<?php echo self::OPT; ?>[theme][radius]" value="sharp" <?php checked($radius==='sharp'); ?>> Sharp
     263            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][radius]" value="sharp" <?php checked($radius==='sharp'); ?>> Sharp
    235264            </label>
    236265
     
    238267            <p style="margin-top:14px;"><strong>Density</strong></p>
    239268            <label style="margin-right:16px;">
    240             <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="compact"  <?php checked($density==='compact');  ?>> Compact
     269            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="compact"  <?php checked($density==='compact');  ?>> Compact
    241270            </label>
    242271            <label style="margin-right:16px;">
    243             <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="normal"   <?php checked($density==='normal');   ?>> Normal
     272            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="normal"   <?php checked($density==='normal');   ?>> Normal
    244273            </label>
    245274            <label>
    246             <input type="radio" name="<?php echo self::OPT; ?>[theme][density]" value="spacious" <?php checked($density==='spacious'); ?>> Spacious
     275            <input type="radio" name="<?php echo esc_attr( self::OPT ); ?>[theme][density]" value="spacious" <?php checked($density==='spacious'); ?>> Spacious
    247276            </label>
    248277
    249278
    250279            <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]">
    252281                <?php foreach ($fonts as $token => $label): ?>
    253282                    <option value="<?php echo esc_attr($token); ?>" <?php selected($font===$token); ?>>
     
    289318                <label style="display:block; margin-bottom:6px;">Text</label>
    290319                <input type="text" class="regular-text"
    291                         name="<?php echo self::OPT; ?>[launcher][text]"
     320                        name="<?php echo esc_attr( self::OPT ); ?>[launcher][text]"
    292321                        value="<?php echo esc_attr($l_text); ?>"
    293322                        placeholder="Chat with our AI" />
     
    297326                    <label style="display:block; margin-bottom:6px;">Background</label>
    298327                    <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]" />
    300329                </span>
    301330                <span>
    302331                    <label style="display:block; margin-bottom:6px;">Text color</label>
    303332                    <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]" />
    305334                </span>
    306335                </p>
     
    320349          <td>
    321350            <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'); ?> />
    323352              Enable floating chat launcher globally
    324353            </label>
     
    349378          'https://cdn.platform.openai.com/deployments/chatkit/chatkit.js',
    350379          [],
    351           null,
     380          self::VER,
    352381          true
    353382      );
     
    357386          plugins_url('assets/bitbloom-agent.js', __FILE__),
    358387          ['openai-chatkit'],
    359           '1.3.1',
     388          self::VER,
    360389          true
    361390      );
     
    474503        $o = get_option(self::OPT, []);
    475504        if (($o['auto_inject'] ?? '') === '1') {
     505            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- embed_markup escapes dynamic values; output is controlled markup.
    476506            echo $this->embed_markup();
     507
    477508            $printed = true;
    478509        }
     
    532563      <?php
    533564        $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 ); ?>
    541573        </button>
    542       <?php
     574        <?php
     575
    543576      return ob_get_clean();
    544577    }
     
    564597                    return new \WP_Error('forbidden', 'Invalid REST nonce', ['status' => 403]);
    565598                }
    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
    569614                $count = (int) get_transient($key);
    570                 if ($count > 20) {
     615
     616                if ($count >= $limit) {
    571617                    return new \WP_Error('rate_limited', 'Too Many Requests', ['status' => 429]);
    572618                }
     619
    573620                set_transient($key, $count + 1, HOUR_IN_SECONDS);
    574621                return true;
     622
    575623            },
    576624        ]);
     
    590638        // Create a stable-but-anonymous "user" string (no PII)
    591639        $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
    594644        $user      = substr(hash('sha256', $site_salt . '|' . $ua_clean), 0, 64);
    595645
  • bitbloom-chatbot-for-chatkit/trunk/readme.txt

    r3396311 r3428474  
    33Tags: openai, chatkit, chat, embed, ai
    44Requires at least: 6.2
    5 Tested up to: 6.8
     5Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.2.0
     7Stable tag: 1.2.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    2121- Theme controls: color scheme, accent, radius, density, font.
    2222- Secure: session created on the server; REST calls nonce-protected.
     23- Rate limiting: configurable per-IP hourly limit for session requests (default 20/hour).
    2324- Privacy-friendly: user ID is a salted hash (no IP stored or sent by the plugin).
    2425
     
    3031== Installation ==
    3132For a full video walkthrough of the installation and setup process:
    32 https://youtu.be/PstZVUaumw8
     33https://youtu.be/Kd0WxODDdYc
    3334
    34351. Download the ZIP and upload it via **Plugins → Add New → Upload Plugin**,
     
    6364   
    6465   [bitbloom_chatbot_for_chatkit]
     66
     678. (Optional) Adjust **Rate limit** (per IP / hour). Default is 20/hour.
     68
    6569
    6670
     
    106110Yes. You can change the color scheme (light/dark), accent color, radius, density, and font.
    107111
     112= Is there a usage/rate limit in the plugin? =
     113Yes. By default, the plugin limits session requests to 20 per hour per IP.
     114You can change this in the plugin settings (Rate limit per IP / hour) to any value between 1 and 99999.
     115
    108116= The chat window shows an error when loading. What causes this? =
    109117Usually one of the following:
     
    140148
    141149== Changelog ==
     150= 1.2.1 =
     151* Added an admin setting to configure the per-IP hourly rate limit for session requests (default: 20/hour).
    142152= 1.2.0 =
    143153* Initial public release under the name “BitBloom ChatKit Embed”.
    144154
    145155== Upgrade Notice ==
     156= 1.2.1 =
     157Adds a configurable rate limit setting (default 20/hour).
    146158= 1.2.0 =
    147159First stable release.
  • bitbloom-chatbot-for-chatkit/trunk/uninstall.php

    r3396302 r3428474  
    11<?php
    2 if ( ! defined('WP_UNINSTALL_PLUGIN') ) {
     2if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
    33    exit;
    44}
    55
    6 $option = 'bitbloom-chatbot-for-chatkit_options';
     6(function () {
     7    $option_name = 'bitbloom-chatbot-for-chatkit_options';
    78
    8 // Delete options (single site)
    9 delete_option($option);
     9    // Delete options (single site).
     10    delete_option( $option_name );
    1011
    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    }
    1516
    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;
    2520
     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.