Plugin Directory

Changeset 3442344


Ignore:
Timestamp:
01/19/2026 09:41:11 AM (7 weeks ago)
Author:
basecloud
Message:

Update to version 1.2.0 from GitHub

Location:
basecloud-shield
Files:
6 edited
1 copied

Legend:

Unmodified
Added
Removed
  • basecloud-shield/tags/1.2.0/basecloud-shield.php

    r3442333 r3442344  
    22/**
    33 * Plugin Name:       BaseCloud Shield
    4  * Description:       Enterprise-grade 2FA security. Supports Central Manager Notifications, WP Email, and SendGrid API.
    5  * Version:           1.0.1
     4 * Description:       Enterprise-grade 2FA security. Supports Central Manager Notifications, WP Email, SendGrid, WhatsApp, SMS, and Webhooks.
     5 * Version:           1.2.0
    66 * Author:            BaseCloud Team
    77 * Author URI:        https://www.basecloudglobal.com/
     
    1515if (!defined('ABSPATH')) { exit; }
    1616
    17 define('BCSHIELD_VERSION', '1.0.1');
     17define('BCSHIELD_VERSION', '1.2.0');
    1818
    1919class BaseCloudShield {
     
    6161        $clean = [];
    6262        $clean['enable_2fa'] = !empty($input['enable_2fa']) ? 1 : 0;
    63         $clean['delivery_method'] = sanitize_text_field($input['delivery_method'] ?? 'email');
    64        
    65         // Central Manager Email (The "Master Key" feature)
     63       
     64        // Multiple delivery methods
     65        $clean['delivery_methods'] = !empty($input['delivery_methods']) && is_array($input['delivery_methods'])
     66            ? array_map('sanitize_text_field', $input['delivery_methods'])
     67            : array('email');
     68       
     69        // User selection for OTP recipients
     70        $clean['recipient_mode'] = sanitize_text_field($input['recipient_mode'] ?? 'user');
    6671        $clean['manager_email'] = sanitize_email($input['manager_email'] ?? '');
    67        
     72        $clean['selected_users'] = !empty($input['selected_users']) && is_array($input['selected_users'])
     73            ? array_map('intval', $input['selected_users'])
     74            : array();
     75       
     76        // Email settings
     77        $clean['from_email'] = sanitize_email($input['from_email'] ?? get_bloginfo('admin_email'));
     78       
     79        // SendGrid settings
     80        $clean['sendgrid_key'] = sanitize_text_field($input['sendgrid_key'] ?? '');
     81       
     82        // Webhook settings
    6883        $clean['webhook_url'] = esc_url_raw($input['webhook_url'] ?? '');
    69         $clean['sendgrid_key'] = sanitize_text_field($input['sendgrid_key'] ?? '');
    70         $clean['from_email'] = sanitize_email($input['from_email'] ?? get_bloginfo('admin_email'));
     84       
     85        // WhatsApp settings
     86        $clean['whatsapp_provider'] = sanitize_text_field($input['whatsapp_provider'] ?? 'twilio');
     87        $clean['whatsapp_account_sid'] = sanitize_text_field($input['whatsapp_account_sid'] ?? '');
     88        $clean['whatsapp_auth_token'] = sanitize_text_field($input['whatsapp_auth_token'] ?? '');
     89        $clean['whatsapp_from'] = sanitize_text_field($input['whatsapp_from'] ?? '');
     90       
     91        // SMS settings
     92        $clean['sms_provider'] = sanitize_text_field($input['sms_provider'] ?? 'twilio');
     93        $clean['sms_account_sid'] = sanitize_text_field($input['sms_account_sid'] ?? '');
     94        $clean['sms_auth_token'] = sanitize_text_field($input['sms_auth_token'] ?? '');
     95        $clean['sms_from'] = sanitize_text_field($input['sms_from'] ?? '');
     96       
    7197        $clean['otp_validity'] = max(1, min(30, intval($input['otp_validity'] ?? 10)));
    7298        return $clean;
     
    88114        set_transient('bcshield_otp_' . $user->ID, $otp, $validity_min * 60);
    89115
    90         // ROUTING LOGIC: Determine Recipient
    91         // If manager_email is set, send there. Otherwise, send to user.
    92         $target_email = !empty($opts['manager_email']) ? $opts['manager_email'] : $user->user_email;
    93 
    94         // DELIVERY ROUTER
    95         $method = $opts['delivery_method'] ?? 'email';
     116        // ROUTING LOGIC: Determine Recipients
     117        $recipients = $this->get_otp_recipients($user, $opts);
     118
     119        // DELIVERY ROUTER - Send via all selected methods
     120        $delivery_methods = isset($opts['delivery_methods']) ? $opts['delivery_methods'] : array('email');
    96121        $from_email = $opts['from_email'] ?? get_bloginfo('admin_email');
    97122       
    98         if ($method === 'webhook' && !empty($opts['webhook_url'])) {
    99             $this->send_via_webhook($user, $otp, $opts['webhook_url']);
    100         }
    101         elseif ($method === 'sendgrid' && !empty($opts['sendgrid_key'])) {
    102             $this->send_via_sendgrid($user, $otp, $target_email, $opts['sendgrid_key'], $from_email);
    103         }
    104         else {
    105             $this->send_via_email($user, $otp, $target_email, $from_email);
     123        foreach ($delivery_methods as $method) {
     124            switch ($method) {
     125                case 'email':
     126                    foreach ($recipients as $recipient) {
     127                        $this->send_via_email($user, $otp, $recipient['email'], $from_email);
     128                    }
     129                    break;
     130                   
     131                case 'sendgrid':
     132                    if (!empty($opts['sendgrid_key'])) {
     133                        foreach ($recipients as $recipient) {
     134                            $this->send_via_sendgrid($user, $otp, $recipient['email'], $opts['sendgrid_key'], $from_email);
     135                        }
     136                    }
     137                    break;
     138                   
     139                case 'webhook':
     140                    if (!empty($opts['webhook_url'])) {
     141                        $this->send_via_webhook($user, $otp, $opts['webhook_url'], $recipients);
     142                    }
     143                    break;
     144                   
     145                case 'whatsapp':
     146                    if (!empty($opts['whatsapp_account_sid']) && !empty($opts['whatsapp_auth_token'])) {
     147                        foreach ($recipients as $recipient) {
     148                            if (!empty($recipient['phone'])) {
     149                                $this->send_via_whatsapp($user, $otp, $recipient['phone'], $opts);
     150                            }
     151                        }
     152                    }
     153                    break;
     154                   
     155                case 'sms':
     156                    if (!empty($opts['sms_account_sid']) && !empty($opts['sms_auth_token'])) {
     157                        foreach ($recipients as $recipient) {
     158                            if (!empty($recipient['phone'])) {
     159                                $this->send_via_sms($user, $otp, $recipient['phone'], $opts);
     160                            }
     161                        }
     162                    }
     163                    break;
     164            }
    106165        }
    107166
     
    113172        exit;
    114173    }
     174   
     175    private function get_otp_recipients($user, $opts) {
     176        $recipients = array();
     177        $mode = $opts['recipient_mode'] ?? 'user';
     178       
     179        if ($mode === 'manager' && !empty($opts['manager_email'])) {
     180            // Send to manager email only
     181            $manager_user = get_user_by('email', $opts['manager_email']);
     182            $recipients[] = array(
     183                'email' => $opts['manager_email'],
     184                'phone' => $manager_user ? get_user_meta($manager_user->ID, 'billing_phone', true) : ''
     185            );
     186        } elseif ($mode === 'selected' && !empty($opts['selected_users'])) {
     187            // Send to selected users
     188            foreach ($opts['selected_users'] as $user_id) {
     189                $selected_user = get_userdata($user_id);
     190                if ($selected_user) {
     191                    $recipients[] = array(
     192                        'email' => $selected_user->user_email,
     193                        'phone' => get_user_meta($user_id, 'billing_phone', true)
     194                    );
     195                }
     196            }
     197        } else {
     198            // Send to the logging-in user
     199            $recipients[] = array(
     200                'email' => $user->user_email,
     201                'phone' => get_user_meta($user->ID, 'billing_phone', true)
     202            );
     203        }
     204       
     205        return $recipients;
     206    }
    115207
    116208    // --- 3. DELIVERY METHODS ---
     
    124216    }
    125217
    126     private function send_via_webhook($user, $otp, $url) {
    127         $body = [
    128             'site_name' => get_bloginfo('name'),
    129             'username'  => $user->user_login,
    130             'email'     => $user->user_email,
    131             'otp_code'  => $otp,
    132             'timestamp' => current_time('mysql')
    133         ];
    134         wp_remote_post($url, ['body' => $body, 'blocking' => true]);
     218    private function send_via_webhook($user, $otp, $url, $recipients = array()) {
     219        $body = array(
     220            'site_name'  => get_bloginfo('name'),
     221            'username'   => $user->user_login,
     222            'email'      => $user->user_email,
     223            'otp_code'   => $otp,
     224            'recipients' => $recipients,
     225            'timestamp'  => current_time('mysql')
     226        );
     227        wp_remote_post($url, array('body' => $body, 'blocking' => true));
     228    }
     229   
     230    private function send_via_whatsapp($user, $otp, $phone, $opts) {
     231        if ($opts['whatsapp_provider'] === 'twilio') {
     232            $url = 'https://api.twilio.com/2010-04-01/Accounts/' . $opts['whatsapp_account_sid'] . '/Messages.json';
     233            $message = "🔐 *" . get_bloginfo('name') . "* Login Alert\n\n";
     234            $message .= "User: " . $user->user_login . "\n";
     235            $message .= "Your OTP code is: *" . $otp . "*\n\n";
     236            $message .= "Valid for " . ($opts['otp_validity'] ?? 10) . " minutes.\n";
     237            $message .= "Secured by BaseCloud Shield";
     238           
     239            wp_remote_post($url, array(
     240                'headers' => array(
     241                    'Authorization' => 'Basic ' . base64_encode($opts['whatsapp_account_sid'] . ':' . $opts['whatsapp_auth_token'])
     242                ),
     243                'body' => array(
     244                    'From' => 'whatsapp:' . $opts['whatsapp_from'],
     245                    'To'   => 'whatsapp:' . $phone,
     246                    'Body' => $message
     247                )
     248            ));
     249        }
     250    }
     251   
     252    private function send_via_sms($user, $otp, $phone, $opts) {
     253        if ($opts['sms_provider'] === 'twilio') {
     254            $url = 'https://api.twilio.com/2010-04-01/Accounts/' . $opts['sms_account_sid'] . '/Messages.json';
     255            $message = get_bloginfo('name') . " Login Alert\n";
     256            $message .= "User: " . $user->user_login . "\n";
     257            $message .= "OTP: " . $otp . "\n";
     258            $message .= "Valid for " . ($opts['otp_validity'] ?? 10) . " min.";
     259           
     260            wp_remote_post($url, array(
     261                'headers' => array(
     262                    'Authorization' => 'Basic ' . base64_encode($opts['sms_account_sid'] . ':' . $opts['sms_auth_token'])
     263                ),
     264                'body' => array(
     265                    'From' => $opts['sms_from'],
     266                    'To'   => $phone,
     267                    'Body' => $message
     268                )
     269            ));
     270        }
    135271    }
    136272
    137273    private function send_via_sendgrid($user, $otp, $to_email, $api_key, $from_email) {
    138274        $url = 'https://api.sendgrid.com/v3/mail/send';
    139         $payload = [
    140             'personalizations' => [[ 'to' => [[ 'email' => $to_email ]] ]],
    141             'from' => [ 'email' => $from_email, 'name' => get_bloginfo('name') ],
     275        $payload = array(
     276            'personalizations' => array(array( 'to' => array(array( 'email' => $to_email )) )),
     277            'from' => array( 'email' => $from_email, 'name' => get_bloginfo('name') ),
    142278            'subject' => 'Login Alert: ' . $user->user_login,
    143             'content' => [[ 'type' => 'text/html', 'value' => $this->get_email_template($user, $otp) ]]
    144         ];
    145         wp_remote_post($url, [
     279            'content' => array(array( 'type' => 'text/html', 'value' => $this->get_email_template($user, $otp) ))
     280        );
     281        wp_remote_post($url, array(
    146282            'method'    => 'POST',
    147283            'body'      => json_encode($payload),
    148             'headers'   => [ 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json' ],
     284            'headers'   => array( 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json' ),
    149285            'blocking'  => true
    150         ]);
     286        ));
    151287    }
    152288
     
    227363        wp_add_inline_style('wp-admin', '
    228364            :root { --bc-bg: #0f2c52; --bc-panel: #163b6b; --bc-green: #4bc46a; --bc-text: #ffffff; }
    229             .bc-wrap { margin: 20px 20px 0 0; max-width: 700px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
     365            .bc-wrap { margin: 20px 20px 0 0; max-width: 800px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
    230366            .bc-container { background-color: var(--bc-bg); border-radius: 15px; padding: 40px; color: var(--bc-text); box-shadow: 0 10px 30px rgba(0,0,0,0.2); }
    231367            .bc-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 25px; margin-bottom: 30px; }
    232368            .bc-header h1 { color: #fff; font-size: 28px; font-weight: 700; margin: 0; }
    233369            .bc-badge { background: var(--bc-green); color: #000; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 700; }
    234             .bc-row { background: var(--bc-panel); padding: 20px 25px; border-radius: 10px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border: 1px solid transparent; transition: 0.3s; }
    235             .bc-row:hover { border-color: rgba(255,255,255,0.2); transform: translateY(-2px); }
     370            .bc-row { background: var(--bc-panel); padding: 20px 25px; border-radius: 10px; margin-bottom: 15px; border: 1px solid transparent; transition: 0.3s; }
     371            .bc-row:hover { border-color: rgba(255,255,255,0.2); }
     372            .bc-row-flex { display: flex; justify-content: space-between; align-items: center; }
    236373            .bc-label strong { display: block; font-size: 15px; margin-bottom: 4px; }
    237374            .bc-label span { font-size: 12px; opacity: 0.7; }
    238375            .bc-input { background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); color: #fff; padding: 10px; border-radius: 6px; width: 300px; }
     376            .bc-input-full { width: 100%; box-sizing: border-box; }
    239377            .bc-select { background: #0a2342; border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 8px; border-radius: 6px; }
     378            .bc-multiselect { background: #0a2342; border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 8px; border-radius: 6px; min-height: 100px; width: 100%; }
    240379            .switch { position: relative; display: inline-block; width: 50px; height: 28px; }
    241380            .switch input { opacity: 0; width: 0; height: 0; }
     
    244383            input:checked + .slider { background-color: var(--bc-green); }
    245384            input:checked + .slider:before { transform: translateX(22px); }
     385            .bc-checkbox-group { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; margin-top: 10px; }
     386            .bc-checkbox-item { background: rgba(0,0,0,0.2); padding: 12px 15px; border-radius: 6px; border: 2px solid transparent; cursor: pointer; transition: 0.2s; }
     387            .bc-checkbox-item:hover { border-color: var(--bc-green); }
     388            .bc-checkbox-item input[type="checkbox"] { margin-right: 8px; }
     389            .bc-checkbox-item label { cursor: pointer; font-size: 14px; }
     390            .bc-config-section { display: none; margin-top: 15px; padding: 15px; background: rgba(0,0,0,0.2); border-radius: 6px; border-left: 3px solid var(--bc-green); }
     391            .bc-config-section.active { display: block; }
     392            .bc-config-row { margin-bottom: 12px; }
     393            .bc-config-row label { display: block; font-size: 13px; margin-bottom: 5px; opacity: 0.9; }
    246394            .bc-save-btn { background: var(--bc-green); width: 100%; color: #0f2c52; border: none; padding: 18px; border-radius: 8px; font-size: 16px; font-weight: 700; text-transform: uppercase; cursor: pointer; margin-top: 20px; transition: 0.3s; }
    247395            .bc-save-btn:hover { background: #fff; }
     396            .bc-section-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); color: var(--bc-green); }
    248397        ');
    249398    }
     
    270419    public function options_page_html() {
    271420        $opts = get_option($this->option_name);
    272         $method = $opts['delivery_method'] ?? 'email';
     421        $delivery_methods = isset($opts['delivery_methods']) ? $opts['delivery_methods'] : array('email');
     422        $recipient_mode = $opts['recipient_mode'] ?? 'user';
     423        $selected_users = isset($opts['selected_users']) ? $opts['selected_users'] : array();
     424       
     425        // Get all WordPress users
     426        $all_users = get_users(array('orderby' => 'display_name'));
    273427        ?>
    274428        <div class="bc-wrap">
    275429            <div class="bc-container">
    276                 <form action="options.php" method="post">
     430                <form action="options.php" method="post" id="bcshield-form">
    277431                    <?php settings_fields('bcshield_options_group'); ?>
    278432                   
     
    282436                    </div>
    283437
    284                     <div class="bc-row">
     438                    <!-- Enable 2FA -->
     439                    <div class="bc-row bc-row-flex">
    285440                        <div class="bc-label">
    286441                            <strong>Enable 2FA</strong>
     
    293448                    </div>
    294449
     450                    <!-- OTP Recipients Section -->
     451                    <div class="bc-section-title">📬 OTP Recipients</div>
     452                   
    295453                    <div class="bc-row">
    296454                        <div class="bc-label">
    297                             <strong>Notification / Manager Email</strong>
    298                             <span>Send ALL codes here. Leave blank to send to User.</span>
     455                            <strong>Who should receive OTP codes?</strong>
     456                            <span>Choose where to send verification codes</span>
     457                        </div>
     458                        <select class="bc-select" name="<?php echo $this->option_name; ?>[recipient_mode]" id="recipient-mode" style="width: 300px;">
     459                            <option value="user" <?php selected('user', $recipient_mode); ?>>Send to Logging-in User</option>
     460                            <option value="manager" <?php selected('manager', $recipient_mode); ?>>Send to Manager Email</option>
     461                            <option value="selected" <?php selected('selected', $recipient_mode); ?>>Send to Selected Users</option>
     462                        </select>
     463                    </div>
     464
     465                    <div class="bc-row" id="manager-email-row" style="display:none;">
     466                        <div class="bc-label">
     467                            <strong>Manager Email</strong>
     468                            <span>All OTP codes will be sent to this email</span>
    299469                        </div>
    300470                        <input type="email" class="bc-input" name="<?php echo $this->option_name; ?>[manager_email]" value="<?php echo esc_attr($opts['manager_email'] ?? ''); ?>" placeholder="manager@example.com">
    301471                    </div>
    302472
     473                    <div class="bc-row" id="selected-users-row" style="display:none;">
     474                        <div class="bc-label">
     475                            <strong>Select Users</strong>
     476                            <span>OTP codes will be sent to these users (hold Ctrl/Cmd to select multiple)</span>
     477                        </div>
     478                        <select multiple class="bc-multiselect" name="<?php echo $this->option_name; ?>[selected_users][]" size="8">
     479                            <?php foreach ($all_users as $user_obj): ?>
     480                                <option value="<?php echo $user_obj->ID; ?>" <?php selected(in_array($user_obj->ID, $selected_users)); ?>>
     481                                    <?php echo esc_html($user_obj->display_name . ' (' . $user_obj->user_email . ')'); ?>
     482                                </option>
     483                            <?php endforeach; ?>
     484                        </select>
     485                    </div>
     486
     487                    <!-- Delivery Methods Section -->
     488                    <div class="bc-section-title">📤 Delivery Methods</div>
     489                   
    303490                    <div class="bc-row">
    304491                        <div class="bc-label">
    305                             <strong>Delivery Method</strong>
    306                             <span>How should OTPs be sent?</span>
    307                         </div>
    308                         <select class="bc-select" name="<?php echo $this->option_name; ?>[delivery_method]">
    309                             <option value="email" <?php selected('email', $method); ?>>Standard WP Email</option>
    310                             <option value="sendgrid" <?php selected('sendgrid', $method); ?>>SendGrid API</option>
    311                             <option value="webhook" <?php selected('webhook', $method); ?>>Webhook</option>
    312                         </select>
    313                     </div>
    314                    
    315                     <div class="bc-row">
    316                         <div class="bc-label">
    317                             <strong>From Email Address</strong>
    318                             <span>Required for SendGrid Verification</span>
    319                         </div>
    320                         <input type="email" class="bc-input" name="<?php echo $this->option_name; ?>[from_email]" value="<?php echo esc_attr($opts['from_email'] ?? get_bloginfo('admin_email')); ?>">
    321                     </div>
    322 
    323                     <div class="bc-row" style="display:block;">
    324                         <div class="bc-label" style="margin-bottom:10px;">
    325                             <strong>SendGrid API Key</strong>
    326                             <span>Required if "SendGrid API" is selected.</span>
    327                         </div>
    328                         <input type="password" class="bc-input" style="width:100%; box-sizing:border-box;" name="<?php echo $this->option_name; ?>[sendgrid_key]" value="<?php echo esc_attr($opts['sendgrid_key'] ?? ''); ?>" placeholder="SG.xxxxxxxx...">
    329                     </div>
    330 
    331                     <div class="bc-row" style="display:block;">
    332                         <div class="bc-label" style="margin-bottom:10px;">
    333                             <strong>Webhook URL</strong>
    334                             <span>Required if "Webhook" is selected.</span>
    335                         </div>
    336                         <input type="url" class="bc-input" style="width:100%; box-sizing:border-box;" name="<?php echo $this->option_name; ?>[webhook_url]" value="<?php echo esc_attr($opts['webhook_url'] ?? ''); ?>" placeholder="https://your-webhook-endpoint.com/path">
    337                     </div>
    338 
    339                     <div class="bc-row">
     492                            <strong>Select Delivery Methods</strong>
     493                            <span>Choose one or more methods to send OTP codes</span>
     494                        </div>
     495                        <div class="bc-checkbox-group">
     496                            <div class="bc-checkbox-item">
     497                                <input type="checkbox" id="method-email" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="email" <?php checked(in_array('email', $delivery_methods)); ?>>
     498                                <label for="method-email">📧 Standard Email</label>
     499                            </div>
     500                            <div class="bc-checkbox-item">
     501                                <input type="checkbox" id="method-sendgrid" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="sendgrid" <?php checked(in_array('sendgrid', $delivery_methods)); ?>>
     502                                <label for="method-sendgrid">📨 SendGrid API</label>
     503                            </div>
     504                            <div class="bc-checkbox-item">
     505                                <input type="checkbox" id="method-webhook" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="webhook" <?php checked(in_array('webhook', $delivery_methods)); ?>>
     506                                <label for="method-webhook">🔗 Webhook</label>
     507                            </div>
     508                            <div class="bc-checkbox-item">
     509                                <input type="checkbox" id="method-whatsapp" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="whatsapp" <?php checked(in_array('whatsapp', $delivery_methods)); ?>>
     510                                <label for="method-whatsapp">💬 WhatsApp</label>
     511                            </div>
     512                            <div class="bc-checkbox-item">
     513                                <input type="checkbox" id="method-sms" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="sms" <?php checked(in_array('sms', $delivery_methods)); ?>>
     514                                <label for="method-sms">📱 SMS</label>
     515                            </div>
     516                        </div>
     517                    </div>
     518
     519                    <!-- Email Configuration -->
     520                    <div class="bc-config-section" id="config-email">
     521                        <h3 style="margin-top:0; font-size: 16px;">📧 Email Configuration</h3>
     522                        <div class="bc-config-row">
     523                            <label>From Email Address</label>
     524                            <input type="email" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[from_email]" value="<?php echo esc_attr($opts['from_email'] ?? get_bloginfo('admin_email')); ?>">
     525                        </div>
     526                    </div>
     527
     528                    <!-- SendGrid Configuration -->
     529                    <div class="bc-config-section" id="config-sendgrid">
     530                        <h3 style="margin-top:0; font-size: 16px;">📨 SendGrid Configuration</h3>
     531                        <div class="bc-config-row">
     532                            <label>SendGrid API Key</label>
     533                            <input type="password" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[sendgrid_key]" value="<?php echo esc_attr($opts['sendgrid_key'] ?? ''); ?>" placeholder="SG.xxxxxxxx...">
     534                        </div>
     535                    </div>
     536
     537                    <!-- Webhook Configuration -->
     538                    <div class="bc-config-section" id="config-webhook">
     539                        <h3 style="margin-top:0; font-size: 16px;">🔗 Webhook Configuration</h3>
     540                        <div class="bc-config-row">
     541                            <label>Webhook URL</label>
     542                            <input type="url" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[webhook_url]" value="<?php echo esc_attr($opts['webhook_url'] ?? ''); ?>" placeholder="https://your-webhook-endpoint.com/path">
     543                        </div>
     544                    </div>
     545
     546                    <!-- WhatsApp Configuration -->
     547                    <div class="bc-config-section" id="config-whatsapp">
     548                        <h3 style="margin-top:0; font-size: 16px;">💬 WhatsApp Configuration</h3>
     549                        <div class="bc-config-row">
     550                            <label>Provider</label>
     551                            <select class="bc-select" name="<?php echo $this->option_name; ?>[whatsapp_provider]">
     552                                <option value="twilio" <?php selected('twilio', $opts['whatsapp_provider'] ?? 'twilio'); ?>>Twilio</option>
     553                            </select>
     554                        </div>
     555                        <div class="bc-config-row">
     556                            <label>Account SID</label>
     557                            <input type="text" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[whatsapp_account_sid]" value="<?php echo esc_attr($opts['whatsapp_account_sid'] ?? ''); ?>" placeholder="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
     558                        </div>
     559                        <div class="bc-config-row">
     560                            <label>Auth Token</label>
     561                            <input type="password" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[whatsapp_auth_token]" value="<?php echo esc_attr($opts['whatsapp_auth_token'] ?? ''); ?>" placeholder="your_auth_token">
     562                        </div>
     563                        <div class="bc-config-row">
     564                            <label>WhatsApp Number (with country code)</label>
     565                            <input type="text" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[whatsapp_from]" value="<?php echo esc_attr($opts['whatsapp_from'] ?? ''); ?>" placeholder="+14155238886">
     566                        </div>
     567                    </div>
     568
     569                    <!-- SMS Configuration -->
     570                    <div class="bc-config-section" id="config-sms">
     571                        <h3 style="margin-top:0; font-size: 16px;">📱 SMS Configuration</h3>
     572                        <div class="bc-config-row">
     573                            <label>Provider</label>
     574                            <select class="bc-select" name="<?php echo $this->option_name; ?>[sms_provider]">
     575                                <option value="twilio" <?php selected('twilio', $opts['sms_provider'] ?? 'twilio'); ?>>Twilio</option>
     576                            </select>
     577                        </div>
     578                        <div class="bc-config-row">
     579                            <label>Account SID</label>
     580                            <input type="text" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[sms_account_sid]" value="<?php echo esc_attr($opts['sms_account_sid'] ?? ''); ?>" placeholder="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
     581                        </div>
     582                        <div class="bc-config-row">
     583                            <label>Auth Token</label>
     584                            <input type="password" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[sms_auth_token]" value="<?php echo esc_attr($opts['sms_auth_token'] ?? ''); ?>" placeholder="your_auth_token">
     585                        </div>
     586                        <div class="bc-config-row">
     587                            <label>SMS Number (with country code)</label>
     588                            <input type="text" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[sms_from]" value="<?php echo esc_attr($opts['sms_from'] ?? ''); ?>" placeholder="+15017122661">
     589                        </div>
     590                    </div>
     591
     592                    <!-- General Settings -->
     593                    <div class="bc-section-title">⚙️ General Settings</div>
     594                   
     595                    <div class="bc-row bc-row-flex">
    340596                        <div class="bc-label">
    341597                            <strong>OTP Validity</strong>
    342598                            <span>Minutes before code expires</span>
    343599                        </div>
    344                         <input type="number" class="bc-input" style="width: 80px;" name="<?php echo $this->option_name; ?>[otp_validity]" value="<?php echo esc_attr($opts['otp_validity'] ?? 10); ?>">
     600                        <input type="number" class="bc-input" style="width: 80px;" name="<?php echo $this->option_name; ?>[otp_validity]" value="<?php echo esc_attr($opts['otp_validity'] ?? 10); ?>" min="1" max="30">
    345601                    </div>
    346602
     
    349605            </div>
    350606        </div>
     607
     608        <script>
     609        (function($) {
     610            $(document).ready(function() {
     611                // Recipient mode toggle
     612                function toggleRecipientFields() {
     613                    const mode = $('#recipient-mode').val();
     614                    $('#manager-email-row, #selected-users-row').hide();
     615                    if (mode === 'manager') {
     616                        $('#manager-email-row').show();
     617                    } else if (mode === 'selected') {
     618                        $('#selected-users-row').show();
     619                    }
     620                }
     621               
     622                $('#recipient-mode').on('change', toggleRecipientFields);
     623                toggleRecipientFields();
     624               
     625                // Delivery method toggle
     626                function toggleDeliveryConfigs() {
     627                    $('.bc-config-section').removeClass('active');
     628                    $('input[name="<?php echo $this->option_name; ?>[delivery_methods][]"]:checked').each(function() {
     629                        const method = $(this).val();
     630                        $('#config-' + method).addClass('active');
     631                    });
     632                }
     633               
     634                $('input[name="<?php echo $this->option_name; ?>[delivery_methods][]"]').on('change', toggleDeliveryConfigs);
     635                toggleDeliveryConfigs();
     636            });
     637        })(jQuery);
     638        </script>
    351639        <?php
    352640    }
  • basecloud-shield/tags/1.2.0/package.json

    r3442297 r3442344  
    11{
    22  "name": "basecloud-shield",
    3   "version": "1.0.0",
     3  "version": "1.1.0",
    44  "description": "WordPress 2FA Security Plugin - Build and deployment scripts",
    55  "scripts": {
     
    2525    "authentication",
    2626    "sendgrid",
     27    "whatsapp",
     28    "sms",
     29    "twilio",
    2730    "login-protection"
    2831  ],
  • basecloud-shield/tags/1.2.0/readme.txt

    r3442333 r3442344  
    11=== BaseCloud Shield ===
    22Contributors: basecloud
    3 Tags: 2fa, security, otp, login protection, sendgrid
     3Tags: 2fa, security, otp, login protection, sendgrid, whatsapp, sms, twilio
    44Requires at least: 5.0
    55Tested up to: 6.9
    6 Stable tag: 1.0.1
     6Stable tag: 1.2.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Enterprise-grade Two-Factor Authentication (2FA) with support for standard Email, SendGrid API, and BaseCloud CRM Webhooks.
     11Enterprise-grade Two-Factor Authentication (2FA) with support for Email, SendGrid API, Webhooks, WhatsApp, and SMS delivery.
    1212
    1313== Description ==
     
    1818
    1919* **Plug & Play:** Works immediately using standard WordPress email delivery.
    20 * **Central Manager Routing:** Option to route ALL login OTPs to a single "Manager Email" address (great for agencies managing client sites).
     20* **Multi-Recipient System:** Send OTPs to the logging-in user, a manager email, or selected users.
     21* **Multi-Channel Delivery:** Choose multiple delivery methods simultaneously (Email, SendGrid, WhatsApp, SMS, Webhook).
     22* **WhatsApp Integration:** Send OTPs directly via WhatsApp using Twilio API.
     23* **SMS Integration:** Deliver OTPs via SMS using Twilio API.
    2124* **SendGrid API V3:** Native integration for high-deliverability emails.
    22 * **BaseCloud CRM Integration:** Connects to BaseCloud Webhooks for advanced automation flows (SMS, WhatsApp, etc).
     25* **Webhook Support:** Connect to custom webhooks for advanced automation flows.
    2326* **Secure OTPs:** 6-digit one-time passwords that expire automatically.
    2427* **Browser Trust:** "Remember this device" functionality to reduce friction for authorized users.
     
    5659**Important**: You must have a SendGrid account and API key to use this feature. You are responsible for complying with SendGrid's terms of service and ensuring proper data handling practices.
    5760
    58 **BaseCloud CRM Webhook (Optional)**
     61**Twilio API for WhatsApp & SMS (Optional)**
    5962
    60 If you select "BaseCloud CRM Webhook" as your delivery method, the plugin will send login notification data to a webhook URL you configure.
     63If you select "WhatsApp" or "SMS" as delivery methods, the plugin will send data to Twilio's API to deliver one-time password codes.
     64
     65* **Service**: Twilio
     66* **What it's used for**: Sending two-factor authentication codes via WhatsApp and/or SMS
     67* **When data is sent**: Every time a user attempts to log in and 2FA is enabled with WhatsApp/SMS selected
     68* **Data sent**:
     69  - Recipient phone number (from user meta field 'billing_phone')
     70  - Sender phone number (WhatsApp number or SMS number configured in settings)
     71  - Site name
     72  - Username attempting to log in
     73  - 6-digit one-time password code
     74  - Message body
     75* **API Endpoint**: https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/Messages.json
     76* **Terms of Service**: https://www.twilio.com/legal/tos
     77* **Privacy Policy**: https://www.twilio.com/legal/privacy
     78
     79**Important**: You must have a Twilio account with WhatsApp and/or SMS capabilities enabled. Phone numbers must be stored in user meta (field: 'billing_phone'). You are responsible for complying with Twilio's terms of service.
     80
     81**Custom Webhook (Optional)**
     82
     83If you select "Webhook" as a delivery method, the plugin will send login notification data to a webhook URL you configure.
    6184
    6285* **Service**: Custom webhook endpoint (configured by you)
    63 * **What it's used for**: Sending login notifications to external systems for custom processing (SMS, WhatsApp, logging, etc.)
     86* **What it's used for**: Sending login notifications to external systems for custom processing
    6487* **When data is sent**: Every time a user attempts to log in and 2FA is enabled
    6588* **Data sent**:
     
    6891  - User email address
    6992  - 6-digit one-time password code
     93  - Recipient information array
    7094  - Timestamp of login attempt
    7195* **Endpoint**: User-configured webhook URL
     
    95119== Changelog ==
    96120
     121= 1.2.0 =
     122**Major Feature Release - Multi-Recipient & Multi-Channel Delivery**
     123
     124• Added Multi-Recipient System with 3 modes:
     125  - Send to Logging-in User (default)
     126  - Send to Manager Email (centralized notifications)
     127  - Send to Selected Users (choose specific users from your site)
     128• Added Multi-Channel Delivery - select multiple delivery methods simultaneously
     129• Added WhatsApp integration via Twilio API
     130• Added SMS integration via Twilio API
     131• Enhanced UI with organized sections and dynamic form fields
     132• User selection interface with multi-select dropdown
     133• Auto-detection of all WordPress users on the site
     134• Smart routing system sends OTP to all selected recipients via all selected methods
     135• Phone number retrieval from user meta (billing_phone field)
     136• Improved settings panel layout with collapsible configuration sections
     137• Each delivery method now has dedicated configuration area
     138• Backward compatible with existing configurations
     139
     140= 1.1.0 =
     141**Internal Development Version**
     142
     143• Pre-release testing version
     144
    97145= 1.0.1 =
    98 **Release Update**
     146**UI Improvements**
    99147
    100 • Bug fixes and improvements
     148• Updated labels and placeholders to be more generic for broader use
     149• Changed "BaseCloud CRM Webhook" to "Webhook" in delivery method options
     150• Removed BaseCloud-specific email placeholders for wider audience compatibility
    101151• Updated version for deployment
    102152
  • basecloud-shield/trunk/basecloud-shield.php

    r3442333 r3442344  
    22/**
    33 * Plugin Name:       BaseCloud Shield
    4  * Description:       Enterprise-grade 2FA security. Supports Central Manager Notifications, WP Email, and SendGrid API.
    5  * Version:           1.0.1
     4 * Description:       Enterprise-grade 2FA security. Supports Central Manager Notifications, WP Email, SendGrid, WhatsApp, SMS, and Webhooks.
     5 * Version:           1.2.0
    66 * Author:            BaseCloud Team
    77 * Author URI:        https://www.basecloudglobal.com/
     
    1515if (!defined('ABSPATH')) { exit; }
    1616
    17 define('BCSHIELD_VERSION', '1.0.1');
     17define('BCSHIELD_VERSION', '1.2.0');
    1818
    1919class BaseCloudShield {
     
    6161        $clean = [];
    6262        $clean['enable_2fa'] = !empty($input['enable_2fa']) ? 1 : 0;
    63         $clean['delivery_method'] = sanitize_text_field($input['delivery_method'] ?? 'email');
    64        
    65         // Central Manager Email (The "Master Key" feature)
     63       
     64        // Multiple delivery methods
     65        $clean['delivery_methods'] = !empty($input['delivery_methods']) && is_array($input['delivery_methods'])
     66            ? array_map('sanitize_text_field', $input['delivery_methods'])
     67            : array('email');
     68       
     69        // User selection for OTP recipients
     70        $clean['recipient_mode'] = sanitize_text_field($input['recipient_mode'] ?? 'user');
    6671        $clean['manager_email'] = sanitize_email($input['manager_email'] ?? '');
    67        
     72        $clean['selected_users'] = !empty($input['selected_users']) && is_array($input['selected_users'])
     73            ? array_map('intval', $input['selected_users'])
     74            : array();
     75       
     76        // Email settings
     77        $clean['from_email'] = sanitize_email($input['from_email'] ?? get_bloginfo('admin_email'));
     78       
     79        // SendGrid settings
     80        $clean['sendgrid_key'] = sanitize_text_field($input['sendgrid_key'] ?? '');
     81       
     82        // Webhook settings
    6883        $clean['webhook_url'] = esc_url_raw($input['webhook_url'] ?? '');
    69         $clean['sendgrid_key'] = sanitize_text_field($input['sendgrid_key'] ?? '');
    70         $clean['from_email'] = sanitize_email($input['from_email'] ?? get_bloginfo('admin_email'));
     84       
     85        // WhatsApp settings
     86        $clean['whatsapp_provider'] = sanitize_text_field($input['whatsapp_provider'] ?? 'twilio');
     87        $clean['whatsapp_account_sid'] = sanitize_text_field($input['whatsapp_account_sid'] ?? '');
     88        $clean['whatsapp_auth_token'] = sanitize_text_field($input['whatsapp_auth_token'] ?? '');
     89        $clean['whatsapp_from'] = sanitize_text_field($input['whatsapp_from'] ?? '');
     90       
     91        // SMS settings
     92        $clean['sms_provider'] = sanitize_text_field($input['sms_provider'] ?? 'twilio');
     93        $clean['sms_account_sid'] = sanitize_text_field($input['sms_account_sid'] ?? '');
     94        $clean['sms_auth_token'] = sanitize_text_field($input['sms_auth_token'] ?? '');
     95        $clean['sms_from'] = sanitize_text_field($input['sms_from'] ?? '');
     96       
    7197        $clean['otp_validity'] = max(1, min(30, intval($input['otp_validity'] ?? 10)));
    7298        return $clean;
     
    88114        set_transient('bcshield_otp_' . $user->ID, $otp, $validity_min * 60);
    89115
    90         // ROUTING LOGIC: Determine Recipient
    91         // If manager_email is set, send there. Otherwise, send to user.
    92         $target_email = !empty($opts['manager_email']) ? $opts['manager_email'] : $user->user_email;
    93 
    94         // DELIVERY ROUTER
    95         $method = $opts['delivery_method'] ?? 'email';
     116        // ROUTING LOGIC: Determine Recipients
     117        $recipients = $this->get_otp_recipients($user, $opts);
     118
     119        // DELIVERY ROUTER - Send via all selected methods
     120        $delivery_methods = isset($opts['delivery_methods']) ? $opts['delivery_methods'] : array('email');
    96121        $from_email = $opts['from_email'] ?? get_bloginfo('admin_email');
    97122       
    98         if ($method === 'webhook' && !empty($opts['webhook_url'])) {
    99             $this->send_via_webhook($user, $otp, $opts['webhook_url']);
    100         }
    101         elseif ($method === 'sendgrid' && !empty($opts['sendgrid_key'])) {
    102             $this->send_via_sendgrid($user, $otp, $target_email, $opts['sendgrid_key'], $from_email);
    103         }
    104         else {
    105             $this->send_via_email($user, $otp, $target_email, $from_email);
     123        foreach ($delivery_methods as $method) {
     124            switch ($method) {
     125                case 'email':
     126                    foreach ($recipients as $recipient) {
     127                        $this->send_via_email($user, $otp, $recipient['email'], $from_email);
     128                    }
     129                    break;
     130                   
     131                case 'sendgrid':
     132                    if (!empty($opts['sendgrid_key'])) {
     133                        foreach ($recipients as $recipient) {
     134                            $this->send_via_sendgrid($user, $otp, $recipient['email'], $opts['sendgrid_key'], $from_email);
     135                        }
     136                    }
     137                    break;
     138                   
     139                case 'webhook':
     140                    if (!empty($opts['webhook_url'])) {
     141                        $this->send_via_webhook($user, $otp, $opts['webhook_url'], $recipients);
     142                    }
     143                    break;
     144                   
     145                case 'whatsapp':
     146                    if (!empty($opts['whatsapp_account_sid']) && !empty($opts['whatsapp_auth_token'])) {
     147                        foreach ($recipients as $recipient) {
     148                            if (!empty($recipient['phone'])) {
     149                                $this->send_via_whatsapp($user, $otp, $recipient['phone'], $opts);
     150                            }
     151                        }
     152                    }
     153                    break;
     154                   
     155                case 'sms':
     156                    if (!empty($opts['sms_account_sid']) && !empty($opts['sms_auth_token'])) {
     157                        foreach ($recipients as $recipient) {
     158                            if (!empty($recipient['phone'])) {
     159                                $this->send_via_sms($user, $otp, $recipient['phone'], $opts);
     160                            }
     161                        }
     162                    }
     163                    break;
     164            }
    106165        }
    107166
     
    113172        exit;
    114173    }
     174   
     175    private function get_otp_recipients($user, $opts) {
     176        $recipients = array();
     177        $mode = $opts['recipient_mode'] ?? 'user';
     178       
     179        if ($mode === 'manager' && !empty($opts['manager_email'])) {
     180            // Send to manager email only
     181            $manager_user = get_user_by('email', $opts['manager_email']);
     182            $recipients[] = array(
     183                'email' => $opts['manager_email'],
     184                'phone' => $manager_user ? get_user_meta($manager_user->ID, 'billing_phone', true) : ''
     185            );
     186        } elseif ($mode === 'selected' && !empty($opts['selected_users'])) {
     187            // Send to selected users
     188            foreach ($opts['selected_users'] as $user_id) {
     189                $selected_user = get_userdata($user_id);
     190                if ($selected_user) {
     191                    $recipients[] = array(
     192                        'email' => $selected_user->user_email,
     193                        'phone' => get_user_meta($user_id, 'billing_phone', true)
     194                    );
     195                }
     196            }
     197        } else {
     198            // Send to the logging-in user
     199            $recipients[] = array(
     200                'email' => $user->user_email,
     201                'phone' => get_user_meta($user->ID, 'billing_phone', true)
     202            );
     203        }
     204       
     205        return $recipients;
     206    }
    115207
    116208    // --- 3. DELIVERY METHODS ---
     
    124216    }
    125217
    126     private function send_via_webhook($user, $otp, $url) {
    127         $body = [
    128             'site_name' => get_bloginfo('name'),
    129             'username'  => $user->user_login,
    130             'email'     => $user->user_email,
    131             'otp_code'  => $otp,
    132             'timestamp' => current_time('mysql')
    133         ];
    134         wp_remote_post($url, ['body' => $body, 'blocking' => true]);
     218    private function send_via_webhook($user, $otp, $url, $recipients = array()) {
     219        $body = array(
     220            'site_name'  => get_bloginfo('name'),
     221            'username'   => $user->user_login,
     222            'email'      => $user->user_email,
     223            'otp_code'   => $otp,
     224            'recipients' => $recipients,
     225            'timestamp'  => current_time('mysql')
     226        );
     227        wp_remote_post($url, array('body' => $body, 'blocking' => true));
     228    }
     229   
     230    private function send_via_whatsapp($user, $otp, $phone, $opts) {
     231        if ($opts['whatsapp_provider'] === 'twilio') {
     232            $url = 'https://api.twilio.com/2010-04-01/Accounts/' . $opts['whatsapp_account_sid'] . '/Messages.json';
     233            $message = "🔐 *" . get_bloginfo('name') . "* Login Alert\n\n";
     234            $message .= "User: " . $user->user_login . "\n";
     235            $message .= "Your OTP code is: *" . $otp . "*\n\n";
     236            $message .= "Valid for " . ($opts['otp_validity'] ?? 10) . " minutes.\n";
     237            $message .= "Secured by BaseCloud Shield";
     238           
     239            wp_remote_post($url, array(
     240                'headers' => array(
     241                    'Authorization' => 'Basic ' . base64_encode($opts['whatsapp_account_sid'] . ':' . $opts['whatsapp_auth_token'])
     242                ),
     243                'body' => array(
     244                    'From' => 'whatsapp:' . $opts['whatsapp_from'],
     245                    'To'   => 'whatsapp:' . $phone,
     246                    'Body' => $message
     247                )
     248            ));
     249        }
     250    }
     251   
     252    private function send_via_sms($user, $otp, $phone, $opts) {
     253        if ($opts['sms_provider'] === 'twilio') {
     254            $url = 'https://api.twilio.com/2010-04-01/Accounts/' . $opts['sms_account_sid'] . '/Messages.json';
     255            $message = get_bloginfo('name') . " Login Alert\n";
     256            $message .= "User: " . $user->user_login . "\n";
     257            $message .= "OTP: " . $otp . "\n";
     258            $message .= "Valid for " . ($opts['otp_validity'] ?? 10) . " min.";
     259           
     260            wp_remote_post($url, array(
     261                'headers' => array(
     262                    'Authorization' => 'Basic ' . base64_encode($opts['sms_account_sid'] . ':' . $opts['sms_auth_token'])
     263                ),
     264                'body' => array(
     265                    'From' => $opts['sms_from'],
     266                    'To'   => $phone,
     267                    'Body' => $message
     268                )
     269            ));
     270        }
    135271    }
    136272
    137273    private function send_via_sendgrid($user, $otp, $to_email, $api_key, $from_email) {
    138274        $url = 'https://api.sendgrid.com/v3/mail/send';
    139         $payload = [
    140             'personalizations' => [[ 'to' => [[ 'email' => $to_email ]] ]],
    141             'from' => [ 'email' => $from_email, 'name' => get_bloginfo('name') ],
     275        $payload = array(
     276            'personalizations' => array(array( 'to' => array(array( 'email' => $to_email )) )),
     277            'from' => array( 'email' => $from_email, 'name' => get_bloginfo('name') ),
    142278            'subject' => 'Login Alert: ' . $user->user_login,
    143             'content' => [[ 'type' => 'text/html', 'value' => $this->get_email_template($user, $otp) ]]
    144         ];
    145         wp_remote_post($url, [
     279            'content' => array(array( 'type' => 'text/html', 'value' => $this->get_email_template($user, $otp) ))
     280        );
     281        wp_remote_post($url, array(
    146282            'method'    => 'POST',
    147283            'body'      => json_encode($payload),
    148             'headers'   => [ 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json' ],
     284            'headers'   => array( 'Authorization' => 'Bearer ' . $api_key, 'Content-Type' => 'application/json' ),
    149285            'blocking'  => true
    150         ]);
     286        ));
    151287    }
    152288
     
    227363        wp_add_inline_style('wp-admin', '
    228364            :root { --bc-bg: #0f2c52; --bc-panel: #163b6b; --bc-green: #4bc46a; --bc-text: #ffffff; }
    229             .bc-wrap { margin: 20px 20px 0 0; max-width: 700px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
     365            .bc-wrap { margin: 20px 20px 0 0; max-width: 800px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
    230366            .bc-container { background-color: var(--bc-bg); border-radius: 15px; padding: 40px; color: var(--bc-text); box-shadow: 0 10px 30px rgba(0,0,0,0.2); }
    231367            .bc-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 25px; margin-bottom: 30px; }
    232368            .bc-header h1 { color: #fff; font-size: 28px; font-weight: 700; margin: 0; }
    233369            .bc-badge { background: var(--bc-green); color: #000; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 700; }
    234             .bc-row { background: var(--bc-panel); padding: 20px 25px; border-radius: 10px; display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; border: 1px solid transparent; transition: 0.3s; }
    235             .bc-row:hover { border-color: rgba(255,255,255,0.2); transform: translateY(-2px); }
     370            .bc-row { background: var(--bc-panel); padding: 20px 25px; border-radius: 10px; margin-bottom: 15px; border: 1px solid transparent; transition: 0.3s; }
     371            .bc-row:hover { border-color: rgba(255,255,255,0.2); }
     372            .bc-row-flex { display: flex; justify-content: space-between; align-items: center; }
    236373            .bc-label strong { display: block; font-size: 15px; margin-bottom: 4px; }
    237374            .bc-label span { font-size: 12px; opacity: 0.7; }
    238375            .bc-input { background: rgba(0,0,0,0.2); border: 1px solid rgba(255,255,255,0.1); color: #fff; padding: 10px; border-radius: 6px; width: 300px; }
     376            .bc-input-full { width: 100%; box-sizing: border-box; }
    239377            .bc-select { background: #0a2342; border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 8px; border-radius: 6px; }
     378            .bc-multiselect { background: #0a2342; border: 1px solid rgba(255,255,255,0.2); color: #fff; padding: 8px; border-radius: 6px; min-height: 100px; width: 100%; }
    240379            .switch { position: relative; display: inline-block; width: 50px; height: 28px; }
    241380            .switch input { opacity: 0; width: 0; height: 0; }
     
    244383            input:checked + .slider { background-color: var(--bc-green); }
    245384            input:checked + .slider:before { transform: translateX(22px); }
     385            .bc-checkbox-group { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; margin-top: 10px; }
     386            .bc-checkbox-item { background: rgba(0,0,0,0.2); padding: 12px 15px; border-radius: 6px; border: 2px solid transparent; cursor: pointer; transition: 0.2s; }
     387            .bc-checkbox-item:hover { border-color: var(--bc-green); }
     388            .bc-checkbox-item input[type="checkbox"] { margin-right: 8px; }
     389            .bc-checkbox-item label { cursor: pointer; font-size: 14px; }
     390            .bc-config-section { display: none; margin-top: 15px; padding: 15px; background: rgba(0,0,0,0.2); border-radius: 6px; border-left: 3px solid var(--bc-green); }
     391            .bc-config-section.active { display: block; }
     392            .bc-config-row { margin-bottom: 12px; }
     393            .bc-config-row label { display: block; font-size: 13px; margin-bottom: 5px; opacity: 0.9; }
    246394            .bc-save-btn { background: var(--bc-green); width: 100%; color: #0f2c52; border: none; padding: 18px; border-radius: 8px; font-size: 16px; font-weight: 700; text-transform: uppercase; cursor: pointer; margin-top: 20px; transition: 0.3s; }
    247395            .bc-save-btn:hover { background: #fff; }
     396            .bc-section-title { font-size: 18px; font-weight: 600; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.1); color: var(--bc-green); }
    248397        ');
    249398    }
     
    270419    public function options_page_html() {
    271420        $opts = get_option($this->option_name);
    272         $method = $opts['delivery_method'] ?? 'email';
     421        $delivery_methods = isset($opts['delivery_methods']) ? $opts['delivery_methods'] : array('email');
     422        $recipient_mode = $opts['recipient_mode'] ?? 'user';
     423        $selected_users = isset($opts['selected_users']) ? $opts['selected_users'] : array();
     424       
     425        // Get all WordPress users
     426        $all_users = get_users(array('orderby' => 'display_name'));
    273427        ?>
    274428        <div class="bc-wrap">
    275429            <div class="bc-container">
    276                 <form action="options.php" method="post">
     430                <form action="options.php" method="post" id="bcshield-form">
    277431                    <?php settings_fields('bcshield_options_group'); ?>
    278432                   
     
    282436                    </div>
    283437
    284                     <div class="bc-row">
     438                    <!-- Enable 2FA -->
     439                    <div class="bc-row bc-row-flex">
    285440                        <div class="bc-label">
    286441                            <strong>Enable 2FA</strong>
     
    293448                    </div>
    294449
     450                    <!-- OTP Recipients Section -->
     451                    <div class="bc-section-title">📬 OTP Recipients</div>
     452                   
    295453                    <div class="bc-row">
    296454                        <div class="bc-label">
    297                             <strong>Notification / Manager Email</strong>
    298                             <span>Send ALL codes here. Leave blank to send to User.</span>
     455                            <strong>Who should receive OTP codes?</strong>
     456                            <span>Choose where to send verification codes</span>
     457                        </div>
     458                        <select class="bc-select" name="<?php echo $this->option_name; ?>[recipient_mode]" id="recipient-mode" style="width: 300px;">
     459                            <option value="user" <?php selected('user', $recipient_mode); ?>>Send to Logging-in User</option>
     460                            <option value="manager" <?php selected('manager', $recipient_mode); ?>>Send to Manager Email</option>
     461                            <option value="selected" <?php selected('selected', $recipient_mode); ?>>Send to Selected Users</option>
     462                        </select>
     463                    </div>
     464
     465                    <div class="bc-row" id="manager-email-row" style="display:none;">
     466                        <div class="bc-label">
     467                            <strong>Manager Email</strong>
     468                            <span>All OTP codes will be sent to this email</span>
    299469                        </div>
    300470                        <input type="email" class="bc-input" name="<?php echo $this->option_name; ?>[manager_email]" value="<?php echo esc_attr($opts['manager_email'] ?? ''); ?>" placeholder="manager@example.com">
    301471                    </div>
    302472
     473                    <div class="bc-row" id="selected-users-row" style="display:none;">
     474                        <div class="bc-label">
     475                            <strong>Select Users</strong>
     476                            <span>OTP codes will be sent to these users (hold Ctrl/Cmd to select multiple)</span>
     477                        </div>
     478                        <select multiple class="bc-multiselect" name="<?php echo $this->option_name; ?>[selected_users][]" size="8">
     479                            <?php foreach ($all_users as $user_obj): ?>
     480                                <option value="<?php echo $user_obj->ID; ?>" <?php selected(in_array($user_obj->ID, $selected_users)); ?>>
     481                                    <?php echo esc_html($user_obj->display_name . ' (' . $user_obj->user_email . ')'); ?>
     482                                </option>
     483                            <?php endforeach; ?>
     484                        </select>
     485                    </div>
     486
     487                    <!-- Delivery Methods Section -->
     488                    <div class="bc-section-title">📤 Delivery Methods</div>
     489                   
    303490                    <div class="bc-row">
    304491                        <div class="bc-label">
    305                             <strong>Delivery Method</strong>
    306                             <span>How should OTPs be sent?</span>
    307                         </div>
    308                         <select class="bc-select" name="<?php echo $this->option_name; ?>[delivery_method]">
    309                             <option value="email" <?php selected('email', $method); ?>>Standard WP Email</option>
    310                             <option value="sendgrid" <?php selected('sendgrid', $method); ?>>SendGrid API</option>
    311                             <option value="webhook" <?php selected('webhook', $method); ?>>Webhook</option>
    312                         </select>
    313                     </div>
    314                    
    315                     <div class="bc-row">
    316                         <div class="bc-label">
    317                             <strong>From Email Address</strong>
    318                             <span>Required for SendGrid Verification</span>
    319                         </div>
    320                         <input type="email" class="bc-input" name="<?php echo $this->option_name; ?>[from_email]" value="<?php echo esc_attr($opts['from_email'] ?? get_bloginfo('admin_email')); ?>">
    321                     </div>
    322 
    323                     <div class="bc-row" style="display:block;">
    324                         <div class="bc-label" style="margin-bottom:10px;">
    325                             <strong>SendGrid API Key</strong>
    326                             <span>Required if "SendGrid API" is selected.</span>
    327                         </div>
    328                         <input type="password" class="bc-input" style="width:100%; box-sizing:border-box;" name="<?php echo $this->option_name; ?>[sendgrid_key]" value="<?php echo esc_attr($opts['sendgrid_key'] ?? ''); ?>" placeholder="SG.xxxxxxxx...">
    329                     </div>
    330 
    331                     <div class="bc-row" style="display:block;">
    332                         <div class="bc-label" style="margin-bottom:10px;">
    333                             <strong>Webhook URL</strong>
    334                             <span>Required if "Webhook" is selected.</span>
    335                         </div>
    336                         <input type="url" class="bc-input" style="width:100%; box-sizing:border-box;" name="<?php echo $this->option_name; ?>[webhook_url]" value="<?php echo esc_attr($opts['webhook_url'] ?? ''); ?>" placeholder="https://your-webhook-endpoint.com/path">
    337                     </div>
    338 
    339                     <div class="bc-row">
     492                            <strong>Select Delivery Methods</strong>
     493                            <span>Choose one or more methods to send OTP codes</span>
     494                        </div>
     495                        <div class="bc-checkbox-group">
     496                            <div class="bc-checkbox-item">
     497                                <input type="checkbox" id="method-email" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="email" <?php checked(in_array('email', $delivery_methods)); ?>>
     498                                <label for="method-email">📧 Standard Email</label>
     499                            </div>
     500                            <div class="bc-checkbox-item">
     501                                <input type="checkbox" id="method-sendgrid" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="sendgrid" <?php checked(in_array('sendgrid', $delivery_methods)); ?>>
     502                                <label for="method-sendgrid">📨 SendGrid API</label>
     503                            </div>
     504                            <div class="bc-checkbox-item">
     505                                <input type="checkbox" id="method-webhook" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="webhook" <?php checked(in_array('webhook', $delivery_methods)); ?>>
     506                                <label for="method-webhook">🔗 Webhook</label>
     507                            </div>
     508                            <div class="bc-checkbox-item">
     509                                <input type="checkbox" id="method-whatsapp" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="whatsapp" <?php checked(in_array('whatsapp', $delivery_methods)); ?>>
     510                                <label for="method-whatsapp">💬 WhatsApp</label>
     511                            </div>
     512                            <div class="bc-checkbox-item">
     513                                <input type="checkbox" id="method-sms" name="<?php echo $this->option_name; ?>[delivery_methods][]" value="sms" <?php checked(in_array('sms', $delivery_methods)); ?>>
     514                                <label for="method-sms">📱 SMS</label>
     515                            </div>
     516                        </div>
     517                    </div>
     518
     519                    <!-- Email Configuration -->
     520                    <div class="bc-config-section" id="config-email">
     521                        <h3 style="margin-top:0; font-size: 16px;">📧 Email Configuration</h3>
     522                        <div class="bc-config-row">
     523                            <label>From Email Address</label>
     524                            <input type="email" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[from_email]" value="<?php echo esc_attr($opts['from_email'] ?? get_bloginfo('admin_email')); ?>">
     525                        </div>
     526                    </div>
     527
     528                    <!-- SendGrid Configuration -->
     529                    <div class="bc-config-section" id="config-sendgrid">
     530                        <h3 style="margin-top:0; font-size: 16px;">📨 SendGrid Configuration</h3>
     531                        <div class="bc-config-row">
     532                            <label>SendGrid API Key</label>
     533                            <input type="password" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[sendgrid_key]" value="<?php echo esc_attr($opts['sendgrid_key'] ?? ''); ?>" placeholder="SG.xxxxxxxx...">
     534                        </div>
     535                    </div>
     536
     537                    <!-- Webhook Configuration -->
     538                    <div class="bc-config-section" id="config-webhook">
     539                        <h3 style="margin-top:0; font-size: 16px;">🔗 Webhook Configuration</h3>
     540                        <div class="bc-config-row">
     541                            <label>Webhook URL</label>
     542                            <input type="url" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[webhook_url]" value="<?php echo esc_attr($opts['webhook_url'] ?? ''); ?>" placeholder="https://your-webhook-endpoint.com/path">
     543                        </div>
     544                    </div>
     545
     546                    <!-- WhatsApp Configuration -->
     547                    <div class="bc-config-section" id="config-whatsapp">
     548                        <h3 style="margin-top:0; font-size: 16px;">💬 WhatsApp Configuration</h3>
     549                        <div class="bc-config-row">
     550                            <label>Provider</label>
     551                            <select class="bc-select" name="<?php echo $this->option_name; ?>[whatsapp_provider]">
     552                                <option value="twilio" <?php selected('twilio', $opts['whatsapp_provider'] ?? 'twilio'); ?>>Twilio</option>
     553                            </select>
     554                        </div>
     555                        <div class="bc-config-row">
     556                            <label>Account SID</label>
     557                            <input type="text" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[whatsapp_account_sid]" value="<?php echo esc_attr($opts['whatsapp_account_sid'] ?? ''); ?>" placeholder="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
     558                        </div>
     559                        <div class="bc-config-row">
     560                            <label>Auth Token</label>
     561                            <input type="password" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[whatsapp_auth_token]" value="<?php echo esc_attr($opts['whatsapp_auth_token'] ?? ''); ?>" placeholder="your_auth_token">
     562                        </div>
     563                        <div class="bc-config-row">
     564                            <label>WhatsApp Number (with country code)</label>
     565                            <input type="text" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[whatsapp_from]" value="<?php echo esc_attr($opts['whatsapp_from'] ?? ''); ?>" placeholder="+14155238886">
     566                        </div>
     567                    </div>
     568
     569                    <!-- SMS Configuration -->
     570                    <div class="bc-config-section" id="config-sms">
     571                        <h3 style="margin-top:0; font-size: 16px;">📱 SMS Configuration</h3>
     572                        <div class="bc-config-row">
     573                            <label>Provider</label>
     574                            <select class="bc-select" name="<?php echo $this->option_name; ?>[sms_provider]">
     575                                <option value="twilio" <?php selected('twilio', $opts['sms_provider'] ?? 'twilio'); ?>>Twilio</option>
     576                            </select>
     577                        </div>
     578                        <div class="bc-config-row">
     579                            <label>Account SID</label>
     580                            <input type="text" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[sms_account_sid]" value="<?php echo esc_attr($opts['sms_account_sid'] ?? ''); ?>" placeholder="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
     581                        </div>
     582                        <div class="bc-config-row">
     583                            <label>Auth Token</label>
     584                            <input type="password" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[sms_auth_token]" value="<?php echo esc_attr($opts['sms_auth_token'] ?? ''); ?>" placeholder="your_auth_token">
     585                        </div>
     586                        <div class="bc-config-row">
     587                            <label>SMS Number (with country code)</label>
     588                            <input type="text" class="bc-input bc-input-full" name="<?php echo $this->option_name; ?>[sms_from]" value="<?php echo esc_attr($opts['sms_from'] ?? ''); ?>" placeholder="+15017122661">
     589                        </div>
     590                    </div>
     591
     592                    <!-- General Settings -->
     593                    <div class="bc-section-title">⚙️ General Settings</div>
     594                   
     595                    <div class="bc-row bc-row-flex">
    340596                        <div class="bc-label">
    341597                            <strong>OTP Validity</strong>
    342598                            <span>Minutes before code expires</span>
    343599                        </div>
    344                         <input type="number" class="bc-input" style="width: 80px;" name="<?php echo $this->option_name; ?>[otp_validity]" value="<?php echo esc_attr($opts['otp_validity'] ?? 10); ?>">
     600                        <input type="number" class="bc-input" style="width: 80px;" name="<?php echo $this->option_name; ?>[otp_validity]" value="<?php echo esc_attr($opts['otp_validity'] ?? 10); ?>" min="1" max="30">
    345601                    </div>
    346602
     
    349605            </div>
    350606        </div>
     607
     608        <script>
     609        (function($) {
     610            $(document).ready(function() {
     611                // Recipient mode toggle
     612                function toggleRecipientFields() {
     613                    const mode = $('#recipient-mode').val();
     614                    $('#manager-email-row, #selected-users-row').hide();
     615                    if (mode === 'manager') {
     616                        $('#manager-email-row').show();
     617                    } else if (mode === 'selected') {
     618                        $('#selected-users-row').show();
     619                    }
     620                }
     621               
     622                $('#recipient-mode').on('change', toggleRecipientFields);
     623                toggleRecipientFields();
     624               
     625                // Delivery method toggle
     626                function toggleDeliveryConfigs() {
     627                    $('.bc-config-section').removeClass('active');
     628                    $('input[name="<?php echo $this->option_name; ?>[delivery_methods][]"]:checked').each(function() {
     629                        const method = $(this).val();
     630                        $('#config-' + method).addClass('active');
     631                    });
     632                }
     633               
     634                $('input[name="<?php echo $this->option_name; ?>[delivery_methods][]"]').on('change', toggleDeliveryConfigs);
     635                toggleDeliveryConfigs();
     636            });
     637        })(jQuery);
     638        </script>
    351639        <?php
    352640    }
  • basecloud-shield/trunk/package.json

    r3442297 r3442344  
    11{
    22  "name": "basecloud-shield",
    3   "version": "1.0.0",
     3  "version": "1.1.0",
    44  "description": "WordPress 2FA Security Plugin - Build and deployment scripts",
    55  "scripts": {
     
    2525    "authentication",
    2626    "sendgrid",
     27    "whatsapp",
     28    "sms",
     29    "twilio",
    2730    "login-protection"
    2831  ],
  • basecloud-shield/trunk/readme.txt

    r3442333 r3442344  
    11=== BaseCloud Shield ===
    22Contributors: basecloud
    3 Tags: 2fa, security, otp, login protection, sendgrid
     3Tags: 2fa, security, otp, login protection, sendgrid, whatsapp, sms, twilio
    44Requires at least: 5.0
    55Tested up to: 6.9
    6 Stable tag: 1.0.1
     6Stable tag: 1.2.0
    77Requires PHP: 7.4
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1010
    11 Enterprise-grade Two-Factor Authentication (2FA) with support for standard Email, SendGrid API, and BaseCloud CRM Webhooks.
     11Enterprise-grade Two-Factor Authentication (2FA) with support for Email, SendGrid API, Webhooks, WhatsApp, and SMS delivery.
    1212
    1313== Description ==
     
    1818
    1919* **Plug & Play:** Works immediately using standard WordPress email delivery.
    20 * **Central Manager Routing:** Option to route ALL login OTPs to a single "Manager Email" address (great for agencies managing client sites).
     20* **Multi-Recipient System:** Send OTPs to the logging-in user, a manager email, or selected users.
     21* **Multi-Channel Delivery:** Choose multiple delivery methods simultaneously (Email, SendGrid, WhatsApp, SMS, Webhook).
     22* **WhatsApp Integration:** Send OTPs directly via WhatsApp using Twilio API.
     23* **SMS Integration:** Deliver OTPs via SMS using Twilio API.
    2124* **SendGrid API V3:** Native integration for high-deliverability emails.
    22 * **BaseCloud CRM Integration:** Connects to BaseCloud Webhooks for advanced automation flows (SMS, WhatsApp, etc).
     25* **Webhook Support:** Connect to custom webhooks for advanced automation flows.
    2326* **Secure OTPs:** 6-digit one-time passwords that expire automatically.
    2427* **Browser Trust:** "Remember this device" functionality to reduce friction for authorized users.
     
    5659**Important**: You must have a SendGrid account and API key to use this feature. You are responsible for complying with SendGrid's terms of service and ensuring proper data handling practices.
    5760
    58 **BaseCloud CRM Webhook (Optional)**
     61**Twilio API for WhatsApp & SMS (Optional)**
    5962
    60 If you select "BaseCloud CRM Webhook" as your delivery method, the plugin will send login notification data to a webhook URL you configure.
     63If you select "WhatsApp" or "SMS" as delivery methods, the plugin will send data to Twilio's API to deliver one-time password codes.
     64
     65* **Service**: Twilio
     66* **What it's used for**: Sending two-factor authentication codes via WhatsApp and/or SMS
     67* **When data is sent**: Every time a user attempts to log in and 2FA is enabled with WhatsApp/SMS selected
     68* **Data sent**:
     69  - Recipient phone number (from user meta field 'billing_phone')
     70  - Sender phone number (WhatsApp number or SMS number configured in settings)
     71  - Site name
     72  - Username attempting to log in
     73  - 6-digit one-time password code
     74  - Message body
     75* **API Endpoint**: https://api.twilio.com/2010-04-01/Accounts/{AccountSid}/Messages.json
     76* **Terms of Service**: https://www.twilio.com/legal/tos
     77* **Privacy Policy**: https://www.twilio.com/legal/privacy
     78
     79**Important**: You must have a Twilio account with WhatsApp and/or SMS capabilities enabled. Phone numbers must be stored in user meta (field: 'billing_phone'). You are responsible for complying with Twilio's terms of service.
     80
     81**Custom Webhook (Optional)**
     82
     83If you select "Webhook" as a delivery method, the plugin will send login notification data to a webhook URL you configure.
    6184
    6285* **Service**: Custom webhook endpoint (configured by you)
    63 * **What it's used for**: Sending login notifications to external systems for custom processing (SMS, WhatsApp, logging, etc.)
     86* **What it's used for**: Sending login notifications to external systems for custom processing
    6487* **When data is sent**: Every time a user attempts to log in and 2FA is enabled
    6588* **Data sent**:
     
    6891  - User email address
    6992  - 6-digit one-time password code
     93  - Recipient information array
    7094  - Timestamp of login attempt
    7195* **Endpoint**: User-configured webhook URL
     
    95119== Changelog ==
    96120
     121= 1.2.0 =
     122**Major Feature Release - Multi-Recipient & Multi-Channel Delivery**
     123
     124• Added Multi-Recipient System with 3 modes:
     125  - Send to Logging-in User (default)
     126  - Send to Manager Email (centralized notifications)
     127  - Send to Selected Users (choose specific users from your site)
     128• Added Multi-Channel Delivery - select multiple delivery methods simultaneously
     129• Added WhatsApp integration via Twilio API
     130• Added SMS integration via Twilio API
     131• Enhanced UI with organized sections and dynamic form fields
     132• User selection interface with multi-select dropdown
     133• Auto-detection of all WordPress users on the site
     134• Smart routing system sends OTP to all selected recipients via all selected methods
     135• Phone number retrieval from user meta (billing_phone field)
     136• Improved settings panel layout with collapsible configuration sections
     137• Each delivery method now has dedicated configuration area
     138• Backward compatible with existing configurations
     139
     140= 1.1.0 =
     141**Internal Development Version**
     142
     143• Pre-release testing version
     144
    97145= 1.0.1 =
    98 **Release Update**
     146**UI Improvements**
    99147
    100 • Bug fixes and improvements
     148• Updated labels and placeholders to be more generic for broader use
     149• Changed "BaseCloud CRM Webhook" to "Webhook" in delivery method options
     150• Removed BaseCloud-specific email placeholders for wider audience compatibility
    101151• Updated version for deployment
    102152
Note: See TracChangeset for help on using the changeset viewer.