Changeset 3419682
- Timestamp:
- 12/15/2025 04:27:39 AM (3 months ago)
- Location:
- cnvrse
- Files:
-
- 173 added
- 4 edited
-
tags/025.12.14.02 (added)
-
tags/025.12.14.02/assets (added)
-
tags/025.12.14.02/assets/css (added)
-
tags/025.12.14.02/assets/css/cnvrse-admin.css (added)
-
tags/025.12.14.02/assets/css/cnvrse-widget.css (added)
-
tags/025.12.14.02/assets/images (added)
-
tags/025.12.14.02/assets/images/cnvrse-icon.jpg (added)
-
tags/025.12.14.02/assets/js (added)
-
tags/025.12.14.02/assets/js/cnvrse-admin.js (added)
-
tags/025.12.14.02/assets/js/cnvrse-core.js (added)
-
tags/025.12.14.02/assets/js/cnvrse-embed.js (added)
-
tags/025.12.14.02/assets/js/cnvrse-visitor.js (added)
-
tags/025.12.14.02/assets/js/cnvrse-widget.js (added)
-
tags/025.12.14.02/cnvrse-lite.php (added)
-
tags/025.12.14.02/languages (added)
-
tags/025.12.14.02/languages/cnvrse.pot (added)
-
tags/025.12.14.02/license.txt (added)
-
tags/025.12.14.02/readme.txt (added)
-
tags/025.12.14.02/src (added)
-
tags/025.12.14.02/src/Actions (added)
-
tags/025.12.14.02/src/Actions/class-cnvrse-admin-hooks.php (added)
-
tags/025.12.14.02/src/Actions/class-cnvrse-conversation-hooks.php (added)
-
tags/025.12.14.02/src/Actions/class-cnvrse-frontend-hooks.php (added)
-
tags/025.12.14.02/src/Actions/class-cnvrse-rest-api-hooks.php (added)
-
tags/025.12.14.02/src/Core (added)
-
tags/025.12.14.02/src/Core/class-cnvrse-conversation-manager.php (added)
-
tags/025.12.14.02/src/Core/class-cnvrse-rest-api-manager.php (added)
-
tags/025.12.14.02/src/Domain (added)
-
tags/025.12.14.02/src/Domain/class-cnvrse-visitor.php (added)
-
tags/025.12.14.02/src/Domain/class-conversation-status.php (added)
-
tags/025.12.14.02/src/Infrastructure (added)
-
tags/025.12.14.02/src/Infrastructure/AdminBar (added)
-
tags/025.12.14.02/src/Infrastructure/AdminBar/class-cnvrse-admin-bar-menu.php (added)
-
tags/025.12.14.02/src/Infrastructure/AdminBar/class-cnvrse-notification-center.php (added)
-
tags/025.12.14.02/src/Infrastructure/AdminBar/class-cnvrse-notification.php (added)
-
tags/025.12.14.02/src/Infrastructure/Geolocation (added)
-
tags/025.12.14.02/src/Infrastructure/Geolocation/Providers (added)
-
tags/025.12.14.02/src/Infrastructure/Geolocation/Providers/class-cnvrse-ipapi-provider.php (added)
-
tags/025.12.14.02/src/Infrastructure/Geolocation/Providers/class-cnvrse-ipinfo-provider.php (added)
-
tags/025.12.14.02/src/Infrastructure/Geolocation/Providers/class-cnvrse-maxmind-provider.php (added)
-
tags/025.12.14.02/src/Infrastructure/Geolocation/class-cnvrse-geolocation.php (added)
-
tags/025.12.14.02/src/Infrastructure/Geolocation/interface-cnvrse-geolocation-provider-interface.php (added)
-
tags/025.12.14.02/src/Infrastructure/Http (added)
-
tags/025.12.14.02/src/Infrastructure/Http/class-cnvrse-cors-handler.php (added)
-
tags/025.12.14.02/src/Infrastructure/Repository (added)
-
tags/025.12.14.02/src/Infrastructure/Repository/class-cnvrse-visitor-repository.php (added)
-
tags/025.12.14.02/src/Infrastructure/Storage (added)
-
tags/025.12.14.02/src/Infrastructure/Storage/class-cnvrse-transient-storage.php (added)
-
tags/025.12.14.02/src/Infrastructure/Telegram (added)
-
tags/025.12.14.02/src/Infrastructure/Telegram/class-cnvrse-telegram.php (added)
-
tags/025.12.14.02/src/Infrastructure/Utilities (added)
-
tags/025.12.14.02/src/Infrastructure/Utilities/class-cnvrse-retry-handler.php (added)
-
tags/025.12.14.02/src/Infrastructure/WordPress (added)
-
tags/025.12.14.02/src/Infrastructure/WordPress/class-cnvrse-cache-buster.php (added)
-
tags/025.12.14.02/src/Infrastructure/WordPress/class-cnvrse-database.php (added)
-
tags/025.12.14.02/src/Infrastructure/WordPress/class-cnvrse-ip-detector.php (added)
-
tags/025.12.14.02/src/Infrastructure/WordPress/class-cnvrse-request-helper.php (added)
-
tags/025.12.14.02/src/Infrastructure/WordPress/class-cnvrse-user-agent-parser.php (added)
-
tags/025.12.14.02/src/Navigation (added)
-
tags/025.12.14.02/src/Navigation/class-cnvrse-admin-menu.php (added)
-
tags/025.12.14.02/src/Presentation (added)
-
tags/025.12.14.02/src/Presentation/Templates (added)
-
tags/025.12.14.02/src/Presentation/Templates/admin-header.php (added)
-
tags/025.12.14.02/src/Presentation/Templates/admin-sidebar.php (added)
-
tags/025.12.14.02/src/Presentation/admin-dashboard-view.php (added)
-
tags/025.12.14.02/src/Presentation/admin-settings-view.php (added)
-
tags/025.12.14.02/src/Presentation/chat-widget-view.php (added)
-
tags/025.12.14.02/src/Presentation/class-cnvrse-inbox-item-renderer.php (added)
-
tags/025.12.14.02/src/Presentation/class-cnvrse-settings-page.php (added)
-
tags/025.12.14.02/src/Presentation/class-cnvrse-system-report.php (added)
-
tags/025.12.14.02/src/Presentation/conversation-columns.php (added)
-
tags/025.12.14.02/src/Presentation/system-status-view.php (added)
-
tags/025.12.14.02/src/REST (added)
-
tags/025.12.14.02/src/REST/Controllers (added)
-
tags/025.12.14.02/src/REST/Controllers/class-cnvrse-rest-admin-controller.php (added)
-
tags/025.12.14.02/src/REST/Controllers/class-cnvrse-rest-base-controller.php (added)
-
tags/025.12.14.02/src/REST/Controllers/class-cnvrse-rest-conversations-controller.php (added)
-
tags/025.12.14.02/src/REST/Controllers/class-cnvrse-rest-telegram-controller.php (added)
-
tags/025.12.14.02/src/REST/Controllers/class-cnvrse-rest-visitors-controller.php (added)
-
tags/025.12.14.02/src/Services (added)
-
tags/025.12.14.02/src/Services/class-cnvrse-dashboard-polling-service.php (added)
-
tags/025.12.14.02/src/Services/class-cnvrse-image-upload-service.php (added)
-
tags/025.12.14.02/src/Services/class-cnvrse-page-view-service.php (added)
-
tags/025.12.14.02/src/Services/class-cnvrse-session-service.php (added)
-
tags/025.12.14.02/src/Services/class-cnvrse-telegram-validation-service.php (added)
-
tags/025.12.14.02/src/Services/class-cnvrse-visitor-tracking-service.php (added)
-
tags/025.12.14.02/src/Shared (added)
-
tags/025.12.14.02/src/Shared/class-tanu-container.php (added)
-
tags/025.12.14.02/src/Shared/class-tanu-security.php (added)
-
tags/025.12.14.02/src/Shared/interface-cnvrse-storage-provider.php (added)
-
tags/025.12.14.02/src/Shared/trait-cnvrse-singleton-trait.php (added)
-
tags/025.12.14.02/src/Utilities (added)
-
tags/025.12.14.02/src/Utilities/class-cnvrse-timezone-helper.php (added)
-
tags/025.12.14.02/src/Utilities/helper-functions.php (added)
-
tags/025.12.14.02/src/Utilities/rest-param-helpers.php (added)
-
tags/025.12.14.02/src/bootstrap.php (added)
-
tags/025.12.14.02/uninstall.php (added)
-
trunk/assets/js/cnvrse-admin.js (added)
-
trunk/assets/js/cnvrse-core.js (added)
-
trunk/assets/js/cnvrse-embed.js (added)
-
trunk/assets/js/cnvrse-visitor.js (added)
-
trunk/assets/js/cnvrse-widget.js (added)
-
trunk/cnvrse-lite.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (1 diff)
-
trunk/src/Actions/class-cnvrse-admin-hooks.php (added)
-
trunk/src/Actions/class-cnvrse-conversation-hooks.php (added)
-
trunk/src/Actions/class-cnvrse-frontend-hooks.php (added)
-
trunk/src/Actions/class-cnvrse-rest-api-hooks.php (added)
-
trunk/src/Core/class-cnvrse-conversation-manager.php (added)
-
trunk/src/Core/class-cnvrse-rest-api-manager.php (added)
-
trunk/src/Domain (added)
-
trunk/src/Domain/class-cnvrse-visitor.php (added)
-
trunk/src/Domain/class-conversation-status.php (added)
-
trunk/src/Infrastructure (added)
-
trunk/src/Infrastructure/AdminBar (added)
-
trunk/src/Infrastructure/AdminBar/class-cnvrse-admin-bar-menu.php (added)
-
trunk/src/Infrastructure/AdminBar/class-cnvrse-notification-center.php (added)
-
trunk/src/Infrastructure/AdminBar/class-cnvrse-notification.php (added)
-
trunk/src/Infrastructure/Geolocation (added)
-
trunk/src/Infrastructure/Geolocation/Providers (added)
-
trunk/src/Infrastructure/Geolocation/Providers/class-cnvrse-ipapi-provider.php (added)
-
trunk/src/Infrastructure/Geolocation/Providers/class-cnvrse-ipinfo-provider.php (added)
-
trunk/src/Infrastructure/Geolocation/Providers/class-cnvrse-maxmind-provider.php (added)
-
trunk/src/Infrastructure/Geolocation/class-cnvrse-geolocation.php (added)
-
trunk/src/Infrastructure/Geolocation/interface-cnvrse-geolocation-provider-interface.php (added)
-
trunk/src/Infrastructure/Http (added)
-
trunk/src/Infrastructure/Http/class-cnvrse-cors-handler.php (added)
-
trunk/src/Infrastructure/Repository (added)
-
trunk/src/Infrastructure/Repository/class-cnvrse-visitor-repository.php (added)
-
trunk/src/Infrastructure/Storage (added)
-
trunk/src/Infrastructure/Storage/class-cnvrse-transient-storage.php (added)
-
trunk/src/Infrastructure/Telegram (added)
-
trunk/src/Infrastructure/Telegram/class-cnvrse-telegram.php (added)
-
trunk/src/Infrastructure/Utilities (added)
-
trunk/src/Infrastructure/Utilities/class-cnvrse-retry-handler.php (added)
-
trunk/src/Infrastructure/WordPress (added)
-
trunk/src/Infrastructure/WordPress/class-cnvrse-cache-buster.php (added)
-
trunk/src/Infrastructure/WordPress/class-cnvrse-database.php (added)
-
trunk/src/Infrastructure/WordPress/class-cnvrse-ip-detector.php (added)
-
trunk/src/Infrastructure/WordPress/class-cnvrse-request-helper.php (added)
-
trunk/src/Infrastructure/WordPress/class-cnvrse-user-agent-parser.php (added)
-
trunk/src/Navigation/class-cnvrse-admin-menu.php (added)
-
trunk/src/Presentation (added)
-
trunk/src/Presentation/Templates (added)
-
trunk/src/Presentation/Templates/admin-header.php (added)
-
trunk/src/Presentation/Templates/admin-sidebar.php (added)
-
trunk/src/Presentation/admin-dashboard-view.php (added)
-
trunk/src/Presentation/admin-settings-view.php (added)
-
trunk/src/Presentation/chat-widget-view.php (added)
-
trunk/src/Presentation/class-cnvrse-inbox-item-renderer.php (added)
-
trunk/src/Presentation/class-cnvrse-settings-page.php (added)
-
trunk/src/Presentation/class-cnvrse-system-report.php (added)
-
trunk/src/Presentation/conversation-columns.php (added)
-
trunk/src/Presentation/system-status-view.php (added)
-
trunk/src/REST (added)
-
trunk/src/REST/Controllers (added)
-
trunk/src/REST/Controllers/class-cnvrse-rest-admin-controller.php (added)
-
trunk/src/REST/Controllers/class-cnvrse-rest-base-controller.php (added)
-
trunk/src/REST/Controllers/class-cnvrse-rest-conversations-controller.php (added)
-
trunk/src/REST/Controllers/class-cnvrse-rest-telegram-controller.php (added)
-
trunk/src/REST/Controllers/class-cnvrse-rest-visitors-controller.php (added)
-
trunk/src/Services (added)
-
trunk/src/Services/class-cnvrse-dashboard-polling-service.php (added)
-
trunk/src/Services/class-cnvrse-image-upload-service.php (added)
-
trunk/src/Services/class-cnvrse-page-view-service.php (added)
-
trunk/src/Services/class-cnvrse-session-service.php (added)
-
trunk/src/Services/class-cnvrse-telegram-validation-service.php (added)
-
trunk/src/Services/class-cnvrse-visitor-tracking-service.php (added)
-
trunk/src/Shared (added)
-
trunk/src/Shared/class-tanu-container.php (added)
-
trunk/src/Shared/class-tanu-security.php (added)
-
trunk/src/Shared/interface-cnvrse-storage-provider.php (added)
-
trunk/src/Shared/trait-cnvrse-singleton-trait.php (added)
-
trunk/src/Utilities/class-cnvrse-timezone-helper.php (added)
-
trunk/src/Utilities/helper-functions.php (modified) (1 diff)
-
trunk/src/Utilities/rest-param-helpers.php (added)
-
trunk/src/bootstrap.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
cnvrse/trunk/cnvrse-lite.php
r3405526 r3419682 4 4 * Plugin URI: https://cnvrse.com/cnvrse-lite 5 5 * Description: Cnvrse Lite is a simple, privacy-friendly live chat widget for WordPress. Reply to your visitors directly from your WordPress Admin or from Telegram. No external accounts required. 6 * Version: 025.1 1.28.016 * Version: 025.12.14.02 7 7 * Author: cnvrse 8 8 * Author URI: https://profiles.wordpress.org/cnvrse/ … … 22 22 23 23 24 define( 'CNVRSE_VERSION', '025.1 1.28.01' );24 define( 'CNVRSE_VERSION', '025.12.14.02' ); 25 25 define( 'CNVRSE_FILE', __FILE__ ); 26 26 define( 'CNVRSE_DIR', plugin_dir_path( __FILE__ ) ); -
cnvrse/trunk/readme.txt
r3405542 r3419682 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 025.1 1.28.017 Stable tag: 025.12.14.02 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html -
cnvrse/trunk/src/Utilities/helper-functions.php
r3405526 r3419682 1 1 <?php 2 2 /** 3 * Helper functions for Cnvrse. 4 * 5 * @package Cnvrse 6 */ 7 8 declare(strict_types=1); 3 * This file is part of Cnvrse Lite . 4 * 5 * Copyright (C) 2010-2025, Renzo Johnson (email: renzo at cnvrse.com) 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code, or visit: 9 * https://www.gnu.org/licenses/gpl-2.0.html 10 */ 11 9 12 10 13 if ( ! defined( 'ABSPATH' ) ) { 11 exit; 12 } 13 14 /* 15 |-------------------------------------------------------------------------- 16 | REST API Helpers 17 |-------------------------------------------------------------------------- 18 */ 19 20 /** 21 * Create a successful API response. 22 */ 23 function cnvrse_api_success( array $data = [] ): WP_REST_Response { 24 return new WP_REST_Response( 25 array_merge( [ 'success' => true ], $data ), 26 200 27 ); 28 } 29 30 /** 31 * Create an error API response. 32 */ 33 function cnvrse_api_error( string $code, string $message, int $status = 200 ): WP_REST_Response { 34 return new WP_REST_Response( 35 [ 36 'success' => false, 37 'code' => $code, 38 'message' => $message, 39 ], 40 $status 41 ); 42 } 43 44 /* 45 |-------------------------------------------------------------------------- 46 | Rate Limiting (extracted from TanuSecurity) 47 |-------------------------------------------------------------------------- 48 */ 49 50 /** 51 * Check rate limit for an action. 52 * 53 * @param string $key Unique key for the action. 54 * @param int $max Maximum attempts allowed. 55 * @param int $window Time window in seconds. 56 * @return bool True if within limit, false if exceeded. 57 */ 58 function cnvrse_check_rate_limit( string $key, int $max = 30, int $window = 60 ): bool { 59 $transient_key = 'cnvrse_rate_' . md5( $key ); 60 $attempts = get_transient( $transient_key ); 61 62 if ( false === $attempts ) { 63 set_transient( $transient_key, 1, $window ); 64 return true; 65 } 66 67 if ( $attempts >= $max ) { 68 return false; 69 } 70 71 set_transient( $transient_key, $attempts + 1, $window ); 72 return true; 73 } 74 75 /* 76 |-------------------------------------------------------------------------- 77 | Validation Helpers (extracted from TanuSecurity) 78 |-------------------------------------------------------------------------- 79 */ 80 81 /** 82 * Validate visitor ID format. 83 * 84 * Format: cnvrse_lite_1234567890_abc123xyz or cnvrse_pro_... 85 */ 86 function cnvrse_validate_visitor_id( string $visitor_id ): string|false { 87 $pattern = '/^cnvrse_(lite|pro)_\d{10,13}_[a-z0-9]{9}$/'; 88 89 return preg_match( $pattern, $visitor_id ) === 1 90 ? sanitize_text_field( $visitor_id ) 91 : false; 92 } 93 94 /** 95 * Validate Telegram bot token format. 96 * 97 * Format: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz 98 */ 99 function cnvrse_validate_bot_token( string $token ): string|false { 100 $pattern = '/^\d{8,10}:[A-Za-z0-9_-]{35}$/'; 101 102 return preg_match( $pattern, $token ) === 1 103 ? sanitize_text_field( $token ) 104 : false; 105 } 106 107 /** 108 * Generate a secure random string. 109 */ 110 function cnvrse_generate_random_string( int $length = 32 ): string { 111 $chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; 112 $str = ''; 113 114 for ( $i = 0; $i < $length; $i++ ) { 115 $str .= $chars[ random_int( 0, strlen( $chars ) - 1 ) ]; 116 } 117 118 return $str; 119 } 120 121 /* 122 |-------------------------------------------------------------------------- 123 | Conversation Helpers 124 |-------------------------------------------------------------------------- 125 */ 126 127 /** 128 * Build conversation query arguments. 129 * 130 * @param string $status Status filter ('all', 'open', 'pending', 'closed'). 131 * @param array $extra Additional query arguments. 132 * @return array Query arguments for get_posts() or ConversationManager. 133 */ 134 function cnvrse_build_conversation_query( string $status = 'all', array $extra = [] ): array { 135 $args = array_merge([ 136 'posts_per_page' => -1, 137 'orderby' => 'modified', 138 'order' => 'DESC', 139 ], $extra ); 140 141 // phpcs:disable WordPress.DB.SlowDBQuery 142 if ( 'all' !== $status && ! empty( $status ) ) { 143 $args['tax_query'] = [[ 144 'taxonomy' => 'cnvrse_status', 145 'field' => 'slug', 146 'terms' => $status, 147 ]]; 148 } 149 // phpcs:enable WordPress.DB.SlowDBQuery 150 151 return $args; 152 } 153 154 /** 155 * Get formatted conversation number. 156 * 157 * Format: #YYMMDD.POSTID.COUNT (e.g., #251028.69.016) 158 */ 159 function cnvrse_get_conversation_number( int $conversation_id, ?string $created_date = null ): string { 160 if ( ! $created_date ) { 161 $post = get_post( $conversation_id ); 162 $created_date = $post?->post_date ?? current_time( 'mysql' ); 163 } 164 165 $date_part = gmdate( 'ymd', strtotime( $created_date ) ); 166 167 // Count conversations on the same day. 168 $conversations = get_posts([ 169 'post_type' => 'cnvrse_conversation', 170 'posts_per_page' => -1, 171 'post_status' => 'publish', 172 'date_query' => [[ 173 'year' => gmdate( 'Y', strtotime( $created_date ) ), 174 'month' => gmdate( 'n', strtotime( $created_date ) ), 175 'day' => gmdate( 'j', strtotime( $created_date ) ), 176 ]], 177 'orderby' => 'date', 178 'order' => 'ASC', 179 'fields' => 'ids', 180 ]); 181 182 $position = array_search( $conversation_id, $conversations, true ); 183 $count = false !== $position ? $position + 1 : count( $conversations ) + 1; 184 $count_part = str_pad( (string) $count, 3, '0', STR_PAD_LEFT ); 185 186 return "#{$date_part}.{$conversation_id}.{$count_part}"; 187 } 188 189 /* 190 |-------------------------------------------------------------------------- 191 | Pro Features 192 |-------------------------------------------------------------------------- 193 */ 194 195 /** 196 * Get Pro upgrade URL. 197 */ 198 function cnvrse_get_pro_url(): string { 199 return 'https://cnvrse.com/pro/'; 200 } 201 202 /** 203 * Check if Pro version is active. 204 */ 205 function cnvrse_is_pro(): bool { 206 return defined( 'CNVRSE_PRO_VERSION' ); 207 } 208 209 /** 210 * Display Pro features notice. 211 */ 212 function cnvrse_show_pro_features(): void { 213 if ( cnvrse_is_pro() ) { 214 return; 215 } 216 217 ?> 218 <div class="cnvrse-pro-notice"> 219 <h3><?php esc_html_e( 'Upgrade to Cnvrse Pro', 'cnvrse' ); ?></h3> 220 <p><?php esc_html_e( 'Get advanced features including multiple agents, canned responses, analytics, and more.', 'cnvrse' ); ?></p> 221 <a href="<?php echo esc_url( cnvrse_get_pro_url() ); ?>" class="button button-primary" target="_blank"> 222 <?php esc_html_e( 'Learn More', 'cnvrse' ); ?> 223 </a> 224 </div> 225 <?php 226 } 14 exit; 15 } 16 17 /** 18 * Get the storage provider instance 19 * 20 * @return Cnvrse_Storage_Provider 21 */ 22 function cnvrse_get_storage_provider() { 23 static $provider = null; 24 25 if ( null === $provider ) { 26 // Allow Pro to override the storage provider 27 if ( has_filter( 'cnvrse_storage_provider' ) ) { 28 $provider = apply_filters( 'cnvrse_storage_provider', null ); 29 } else { 30 // Default to container resolution (TransientStorage for Lite) 31 $provider = cnvrse_make( 'Cnvrse_Storage_Provider' ); 32 } 33 } 34 35 return $provider; 36 } 37 38 /** 39 * Get Pro upgrade URL 40 * 41 * @return string 42 */ 43 function cnvrse_get_pro_url() { 44 return apply_filters( 45 'cnvrse_pro_upgrade_url', 46 'https://cnvrse.com/pro/?utm_source=lite&utm_medium=admin&utm_campaign=upgrade' 47 ); 48 } 49 50 /** 51 * Get formatted conversation number (e.g., #251027.012) 52 * 53 * PERFORMANCE FIX: Now reads from _cnvrse_chat_number meta field (stored on creation) 54 * Falls back to generation if meta doesn't exist (for backward compatibility) 55 * 56 * Format: #YYMMDD.XXX where XXX is the conversation count for that day 57 * 58 * @param int $conversation_id Conversation post ID 59 * @param string $created_date Conversation creation date (optional, for generation fallback) 60 * @return string Formatted conversation number 61 */ 62 function cnvrse_get_conversation_number( $conversation_id, $created_date = null ) { 63 // Try to get from meta first (fast path) 64 $chat_number = get_post_meta( $conversation_id, '_cnvrse_chat_number', true ); 65 66 if ( ! empty( $chat_number ) ) { 67 return $chat_number; 68 } 69 70 // Fallback: Generate and store (for backward compatibility with old conversations) 71 $chat_number = cnvrse_generate_chat_number( $conversation_id, $created_date ); 72 73 // Store for future requests 74 update_post_meta( $conversation_id, '_cnvrse_chat_number', $chat_number ); 75 76 return $chat_number; 77 } 78 79 /** 80 * Generate chat number (internal function - called once per conversation) 81 * 82 * Format: #YYMMDD.POSTID.COUNT 83 * 84 * @param int $conversation_id Conversation post ID 85 * @param string $created_date Conversation creation date (optional) 86 * @return string Formatted conversation number 87 */ 88 function cnvrse_generate_chat_number( $conversation_id, $created_date = null ) { 89 // Get creation date 90 if ( ! $created_date ) { 91 $post = get_post( $conversation_id ); 92 $created_date = $post ? $post->post_date : current_time( 'mysql' ); 93 } 94 95 // Format: YYMMDD 96 $date_part = gmdate( 'ymd', strtotime( $created_date ) ); 97 98 // Count conversations created on the same day (before this one) 99 $args = array( 100 'post_type' => 'cnvrse_conversation', 101 'posts_per_page' => -1, 102 'post_status' => 'publish', 103 'date_query' => array( 104 array( 105 'year' => gmdate( 'Y', strtotime( $created_date ) ), 106 'month' => gmdate( 'n', strtotime( $created_date ) ), 107 'day' => gmdate( 'j', strtotime( $created_date ) ), 108 ), 109 ), 110 'orderby' => 'date', 111 'order' => 'ASC', 112 'fields' => 'ids', 113 ); 114 115 $conversations = get_posts( $args ); 116 $position = array_search( $conversation_id, $conversations ); 117 $count = false !== $position ? $position + 1 : count( $conversations ) + 1; 118 119 // Format: XXX (3 digits, zero-padded) 120 $count_part = str_pad( $count, 3, '0', STR_PAD_LEFT ); 121 122 // Format: #YYMMDD.POSTID.COUNT (e.g., #251028.69.016) 123 return '#' . $date_part . '.' . $conversation_id . '.' . $count_part; 124 } 125 126 /** 127 * Detect visitor tier based on message engagement and contact identification 128 * 129 * Tier hierarchy (Intercom industry standard): 130 * - Visitor: Browsing only, no messages sent (message_count = 0) 131 * - Conversation: Has sent messages (message_count > 0) but no email/name captured 132 * - User: Has email OR name captured (identified contact) 133 * 134 * @param int $conversation_id Conversation post ID. 135 * @return string|false 'user', 'conversation', 'visitor', or false on error. 136 */ 137 function cnvrse_detect_visitor_tier( $conversation_id ) { 138 // Remove capability check - it causes issues during hydration if context is slightly off 139 // Callers (REST API, Admin Page) already handle permission checks 140 141 // Validate conversation ID 142 if ( ! is_numeric( $conversation_id ) ) { 143 return false; 144 } 145 146 $conversation_id = (int) $conversation_id; 147 148 // Verify it's actually a conversation post 149 if ( 'cnvrse_conversation' !== get_post_type( $conversation_id ) ) { 150 return false; 151 } 152 153 // Get REAL message count to determine engagement level 154 // Excludes system messages like "Visitor returned" which don't indicate engagement 155 $db = Cnvrse_Database::instance(); 156 $message_count = $db->get_real_message_count( $conversation_id ); 157 158 // Get contact identification (email or name) 159 $email = get_post_meta( $conversation_id, '_cnvrse_visitor_email', true ) ?? ''; 160 $name = get_post_meta( $conversation_id, '_cnvrse_visitor_name', true ) ?? ''; 161 162 // Tier 1: Visitor (browsing only, no messages sent) 163 if ( 0 === $message_count ) { 164 return 'visitor'; 165 } 166 167 // Tier 2: Conversation (has engaged but not identified) 168 // Message count > 0 but no email/name captured 169 if ( empty( $email ) && empty( $name ) ) { 170 return 'conversation'; 171 } 172 173 // Tier 3: User (identified contact with email or name) 174 return 'user'; 175 } 176 177 /** 178 * Get unread message count for conversation 179 * 180 * @param int $conversation_id Conversation post ID. 181 * @return int Unread count. 182 */ 183 function cnvrse_get_unread_count( $conversation_id ) { 184 // Add capability check 185 if ( ! current_user_can( 'manage_options' ) ) { 186 return 0; 187 } 188 189 // Use existing Database class method 190 $db = Cnvrse_Database::instance(); 191 return (int) $db->get_unread_count( $conversation_id ); 192 } 193 194 /** 195 * Update last activity timestamp for conversation 196 * 197 * Universal function that works for both local and satellite conversations. 198 * Updates _cnvrse_last_activity meta field with current Unix timestamp. 199 * 200 * This function is called automatically on: 201 * - Message sent (visitor or admin) 202 * - Page view tracked 203 * - Chat started 204 * - Session ended 205 * 206 * @since 0.9.13 207 * @param int $conversation_id Conversation post ID. 208 * @return bool True on success, false on failure. 209 */ 210 function cnvrse_update_last_activity( $conversation_id ) { 211 // Validate conversation ID 212 if ( empty( $conversation_id ) || ! is_numeric( $conversation_id ) ) { 213 return false; 214 } 215 216 $conversation_id = (int) $conversation_id; 217 218 // Verify it's a conversation post 219 if ( 'cnvrse_conversation' !== get_post_type( $conversation_id ) ) { 220 return false; 221 } 222 223 // Use pure UTC Unix timestamp (matches messages table and allows direct comparison) 224 // Note: current_time('timestamp') applies timezone offset, causing 5-hour discrepancy 225 $timestamp = time(); 226 227 // Update meta field 228 $result = update_post_meta( $conversation_id, '_cnvrse_last_activity', $timestamp ); 229 230 return $result; 231 } -
cnvrse/trunk/src/bootstrap.php
r3405526 r3419682 1 1 <?php 2 2 /** 3 * Bootstrap file for Cnvrse. 4 * 5 * @package Cnvrse 6 */ 7 8 declare(strict_types=1); 3 * Cnvrse Bootstrap 4 * 5 * Single entry point for the Cnvrse plugin. 6 * Contains: Autoloader, Main Class, and initialization. 7 * 8 * This file is part of Cnvrse Lite. 9 * 10 * Copyright (C) 2010-2025, Renzo Johnson (email: renzo at cnvrse.com) 11 * 12 * For the full copyright and license information, please view the LICENSE 13 * file that was distributed with this source code, or visit: 14 * https://www.gnu.org/licenses/gpl-2.0.html 15 * 16 * @package Cnvrse_Lite 17 * @since 2.5.0 18 */ 9 19 10 20 if ( ! defined( 'ABSPATH' ) ) { 11 exit; 12 } 13 14 /** 15 * Main Cnvrse plugin class. 21 exit; 22 } 23 24 /* 25 |-------------------------------------------------------------------------- 26 | Autoloader 27 |-------------------------------------------------------------------------- 28 */ 29 30 /** 31 * Class Cnvrse_Autoloader 32 * 33 * Convention-based autoloader for the Cnvrse plugin. 34 * No class map needed - derives file paths from class names. 35 * 36 * Conventions: 37 * - Classes: class-{name}.php (e.g., Cnvrse_Database → class-cnvrse-database.php) 38 * - Interfaces: interface-{name}.php (e.g., Cnvrse_Storage_Provider → interface-cnvrse-storage-provider.php) 39 * - Traits: trait-{name}.php (e.g., Cnvrse_Singleton_Trait → trait-cnvrse-singleton.php) 40 */ 41 final class Cnvrse_Autoloader { 42 43 /** 44 * Base path for class files. 45 * 46 * @var string 47 */ 48 private static $base_path = ''; 49 50 /** 51 * Directories to search (in order). 52 * 53 * @var array 54 */ 55 private static $search_dirs = array( 56 'Actions/', 57 'Core/', 58 'Domain/', 59 'Infrastructure/AdminBar/', 60 'Infrastructure/Geolocation/', 61 'Infrastructure/Geolocation/Providers/', 62 'Infrastructure/Http/', 63 'Infrastructure/Repository/', 64 'Infrastructure/Storage/', 65 'Infrastructure/Telegram/', 66 'Infrastructure/Utilities/', 67 'Infrastructure/WordPress/', 68 'Navigation/', 69 'Presentation/', 70 'REST/Controllers/', 71 'Services/', 72 'Shared/', 73 'Utilities/', 74 ); 75 76 /** 77 * Register the autoloader. 78 * 79 * @param string $base_path Base path for src/ directory. 80 * @return void 81 */ 82 public static function register( string $base_path ): void { 83 self::$base_path = trailingslashit( $base_path ); 84 spl_autoload_register( array( __CLASS__, 'autoload' ) ); 85 } 86 87 /** 88 * Autoload callback. 89 * 90 * @param string $class_name Class name to load. 91 * @return void 92 */ 93 public static function autoload( string $class_name ): void { 94 // Handle namespaced classes (e.g., Cnvrse\Shared\Tanu_Container). 95 if ( strpos( $class_name, 'Cnvrse\\' ) === 0 ) { 96 $parts = explode( '\\', $class_name ); 97 $short_class = end( $parts ); 98 $namespace_dir = implode( '/', array_slice( $parts, 1, -1 ) ); 99 $base_name = strtolower( str_replace( '_', '-', $short_class ) ) . '.php'; 100 101 // Try class-, interface-, trait- prefixes for namespaced types. 102 foreach ( array( 'class-', 'interface-', 'trait-' ) as $prefix ) { 103 $file = self::$base_path . $namespace_dir . '/' . $prefix . $base_name; 104 if ( file_exists( $file ) ) { 105 require_once $file; 106 return; 107 } 108 } 109 } 110 111 // Skip non-Cnvrse/Tanu classes. 112 if ( strpos( $class_name, 'Cnvrse' ) !== 0 && strpos( $class_name, 'Tanu' ) !== 0 ) { 113 return; 114 } 115 116 // Determine file prefix based on naming convention. 117 $prefix = 'class-'; 118 if ( strpos( $class_name, '_Trait' ) !== false ) { 119 $prefix = 'trait-'; 120 } elseif ( strpos( $class_name, '_Interface' ) !== false ) { 121 $prefix = 'interface-'; 122 } 123 124 // Special case: Cnvrse_Storage_Provider is an interface. 125 if ( 'Cnvrse_Storage_Provider' === $class_name ) { 126 $prefix = 'interface-'; 127 } 128 129 // Convert class name to file name: Cnvrse_Admin_Hooks → cnvrse-admin-hooks. 130 $file_name = $prefix . strtolower( str_replace( '_', '-', $class_name ) ) . '.php'; 131 132 // Search in all directories. 133 foreach ( self::$search_dirs as $dir ) { 134 $file = self::$base_path . $dir . $file_name; 135 if ( file_exists( $file ) ) { 136 require_once $file; 137 return; 138 } 139 } 140 } 141 } 142 143 // Register autoloader. 144 Cnvrse_Autoloader::register( __DIR__ . '/' ); 145 146 /* 147 |-------------------------------------------------------------------------- 148 | DI Container (global functions) 149 |-------------------------------------------------------------------------- 150 */ 151 152 /** 153 * Get the DI container instance 154 * 155 * @return \Cnvrse\Shared\Tanu_Container 156 */ 157 function cnvrse_container() { 158 static $container = null; 159 160 if ( null === $container ) { 161 $container = new \Cnvrse\Shared\Tanu_Container(); 162 163 // Bind storage provider - Lite uses transient storage. 164 $storage_class = apply_filters( 'cnvrse_storage_provider_class', \Cnvrse\Infrastructure\Storage\Cnvrse_Transient_Storage::class ); 165 $container->bind( 'Cnvrse_Storage_Provider', $storage_class ); 166 $container->singleton( $storage_class ); 167 168 // Allow Pro to modify bindings. 169 do_action( 'cnvrse_register_bindings', $container ); 170 } 171 172 return $container; 173 } 174 175 /** 176 * Resolve from container 177 * 178 * @param string $abstract Class or interface to resolve. 179 * @return mixed 180 */ 181 function cnvrse_make( string $abstract ) { 182 return cnvrse_container()->make( $abstract ); 183 } 184 185 /* 186 |-------------------------------------------------------------------------- 187 | Helper Functions & Templates 188 |-------------------------------------------------------------------------- 189 */ 190 191 // Load helper functions (not autoloadable - procedural code). 192 require_once __DIR__ . '/Utilities/helper-functions.php'; 193 require_once __DIR__ . '/Utilities/rest-param-helpers.php'; 194 195 // Load templates (not autoloadable - pure rendering). 196 require_once __DIR__ . '/Presentation/conversation-columns.php'; 197 require_once __DIR__ . '/Presentation/chat-widget-view.php'; 198 199 /* 200 |-------------------------------------------------------------------------- 201 | Main Plugin Class 202 |-------------------------------------------------------------------------- 203 */ 204 205 /** 206 * Main Cnvrse Plugin Class 207 * 208 * @since 1.0.0 16 209 */ 17 210 final class Cnvrse { 18 211 19 private static ?self $instance = null; 20 21 public static function instance(): self { 22 return self::$instance ??= new self(); 23 } 24 25 private function __construct() { 26 $this->load_dependencies(); 27 $this->init_hooks(); 28 } 29 30 /** 31 * Load required files. 32 */ 33 private function load_dependencies(): void { 34 // Utilities. 35 require_once CNVRSE_DIR . 'src/Utilities/helper-functions.php'; 36 require_once CNVRSE_DIR . 'src/Core/visitor-auth-helpers.php'; 37 38 // Core classes. 39 require_once CNVRSE_DIR . 'src/Core/Database.php'; 40 require_once CNVRSE_DIR . 'src/Core/CacheBuster.php'; 41 require_once CNVRSE_DIR . 'src/Core/TelegramClient.php'; 42 require_once CNVRSE_DIR . 'src/Core/ConversationManager.php'; 43 require_once CNVRSE_DIR . 'src/Core/RestApiManager.php'; 44 45 // API layer. 46 require_once CNVRSE_DIR . 'src/Api/routes.php'; 47 require_once CNVRSE_DIR . 'src/Api/VisitorEndpoints.php'; 48 require_once CNVRSE_DIR . 'src/Api/AdminEndpoints.php'; 49 require_once CNVRSE_DIR . 'src/Api/TelegramEndpoints.php'; 50 51 // Hooks. 52 require_once CNVRSE_DIR . 'src/Actions/conversation-hooks.php'; 53 require_once CNVRSE_DIR . 'src/Actions/frontend-hooks.php'; 54 55 if ( is_admin() ) { 56 require_once CNVRSE_DIR . 'src/Actions/admin-hooks.php'; 57 require_once CNVRSE_DIR . 'src/Navigation/admin-menu.php'; 58 } 59 60 // Templates. 61 require_once CNVRSE_DIR . 'src/Templates/conversation-columns.php'; 62 require_once CNVRSE_DIR . 'src/Templates/chat-widget-view.php'; 63 64 if ( is_admin() ) { 65 require_once CNVRSE_DIR . 'src/Templates/admin-dashboard-view.php'; 66 require_once CNVRSE_DIR . 'src/Templates/admin-settings-view.php'; 67 } 68 } 69 70 /** 71 * Initialize hooks. 72 */ 73 private function init_hooks(): void { 74 register_activation_hook( CNVRSE_FILE, [ $this, 'activate' ] ); 75 register_deactivation_hook( CNVRSE_FILE, [ $this, 'deactivate' ] ); 76 77 add_action( 'init', [ $this, 'init' ] ); 78 add_action( 'plugins_loaded', [ $this, 'loaded' ] ); 79 } 80 81 /** 82 * Plugin activation. 83 */ 84 public function activate(): void { 85 Cnvrse_Database::instance()->create_tables(); 86 87 // Generate Telegram webhook secret. 88 if ( ! get_option( 'cnvrse_telegram_webhook_secret' ) ) { 89 update_option( 'cnvrse_telegram_webhook_secret', bin2hex( random_bytes( 32 ) ) ); 90 } 91 92 flush_rewrite_rules(); 93 set_transient( 'cnvrse_activation_redirect', true, 30 ); 94 } 95 96 /** 97 * Plugin deactivation. 98 */ 99 public function deactivate(): void { 100 flush_rewrite_rules(); 101 } 102 103 /** 104 * Init hook. 105 */ 106 public function init(): void { 107 $this->register_post_type(); 108 $this->register_taxonomy(); 109 110 load_plugin_textdomain( 'cnvrse', false, dirname( plugin_basename( CNVRSE_FILE ) ) . '/languages' ); 111 } 112 113 /** 114 * Plugins loaded hook. 115 */ 116 public function loaded(): void { 117 // Schedule cleanup. 118 if ( ! wp_next_scheduled( 'cnvrse_cleanup_transients' ) ) { 119 wp_schedule_event( time(), 'hourly', 'cnvrse_cleanup_transients' ); 120 } 121 122 add_action( 'cnvrse_cleanup_transients', [ $this, 'cleanup_expired_data' ] ); 123 } 124 125 /** 126 * Register conversation post type. 127 */ 128 private function register_post_type(): void { 129 register_post_type( 'cnvrse_conversation', [ 130 'labels' => [ 131 'name' => __( 'Conversations', 'cnvrse' ), 132 'singular_name' => __( 'Conversation', 'cnvrse' ), 133 ], 134 'public' => false, 135 'show_ui' => false, 136 'show_in_rest' => false, 137 'supports' => [ 'title' ], 138 'capability_type' => 'post', 139 'map_meta_cap' => true, 140 'exclude_from_search' => true, 141 ]); 142 } 143 144 /** 145 * Register status taxonomy. 146 */ 147 private function register_taxonomy(): void { 148 register_taxonomy( 'cnvrse_status', 'cnvrse_conversation', [ 149 'labels' => [ 150 'name' => __( 'Status', 'cnvrse' ), 151 'singular_name' => __( 'Status', 'cnvrse' ), 152 ], 153 'public' => false, 154 'hierarchical' => false, 155 'show_ui' => false, 156 'show_in_rest' => false, 157 ]); 158 159 // Create default terms. 160 foreach ( [ 'open', 'pending', 'closed' ] as $status ) { 161 if ( ! term_exists( $status, 'cnvrse_status' ) ) { 162 wp_insert_term( ucfirst( $status ), 'cnvrse_status', [ 'slug' => $status ] ); 163 } 164 } 165 } 166 167 /** 168 * Cleanup expired data. 169 */ 170 public function cleanup_expired_data(): void { 171 ConversationManager::instance()->auto_close_inactive( 24 ); 172 } 173 } 174 175 // Initialize. 176 Cnvrse::instance(); 212 /** 213 * Singleton instance 214 * 215 * @var Cnvrse 216 */ 217 private static $instance = null; 218 219 /** 220 * Get singleton instance 221 * 222 * @return Cnvrse 223 */ 224 public static function instance() { 225 if ( null === self::$instance ) { 226 self::$instance = new self(); 227 } 228 return self::$instance; 229 } 230 231 /** 232 * Constructor 233 */ 234 private function __construct() { 235 $this->init_components(); 236 $this->init_hooks(); 237 } 238 239 /** 240 * Initialize plugin components. 241 * 242 * @return void 243 */ 244 private function init_components(): void { 245 // Initialize hook handlers (singletons register their hooks). 246 Cnvrse_Conversation_Hooks::instance(); 247 Cnvrse_REST_API_Hooks::instance(); 248 Cnvrse_Frontend_Hooks::instance(); 249 250 // Initialize notification center (works on frontend and admin). 251 Cnvrse_Notification_Center::get(); 252 253 // Admin bar menu (shows on both frontend and admin when logged in). 254 Cnvrse_Admin_Bar_Menu::instance(); 255 256 if ( is_admin() ) { 257 Cnvrse_Admin_Hooks::instance(); 258 Cnvrse_Admin_Menu::instance(); 259 260 // Load admin-only templates. 261 require_once __DIR__ . '/Presentation/admin-dashboard-view.php'; 262 require_once __DIR__ . '/Presentation/admin-settings-view.php'; 263 require_once __DIR__ . '/Presentation/system-status-view.php'; 264 } 265 } 266 267 /** 268 * Initialize hooks 269 * 270 * @return void 271 */ 272 private function init_hooks(): void { 273 register_activation_hook( CNVRSE_FILE, array( $this, 'activate' ) ); 274 register_deactivation_hook( CNVRSE_FILE, array( $this, 'deactivate' ) ); 275 276 add_action( 'init', array( $this, 'init' ) ); 277 add_action( 'plugins_loaded', array( $this, 'loaded' ) ); 278 279 // CORS Security: Allow X-Cnvrse-Site-Key header. 280 add_filter( 'rest_allowed_cors_headers', array( $this, 'allow_site_key_header' ), 10, 2 ); 281 282 // CORS Security: Restrict origins to registered satellite sites. 283 add_filter( 'allowed_http_origins', array( $this, 'allow_registered_origins' ) ); 284 285 // Schedule cleanup. 286 add_action( 'cnvrse_cleanup_transients', array( $this, 'cleanup_expired_data' ) ); 287 } 288 289 /** 290 * Allow X-Cnvrse-Site-Key header 291 * 292 * @param array $allow_headers Headers to allow. 293 * @param WP_REST_Request $request Request object. 294 * @return array 295 */ 296 public function allow_site_key_header( $allow_headers, $request ) { 297 $allow_headers[] = 'X-Cnvrse-Site-Key'; 298 return $allow_headers; 299 } 300 301 /** 302 * Allow registered satellite origins for CORS 303 * 304 * @param array $origins Allowed origins. 305 * @return array 306 */ 307 public function allow_registered_origins( $origins ) { 308 $satellites = get_option( 'cnvrse_hub_registered_sites', array() ); 309 310 if ( ! is_array( $satellites ) ) { 311 return $origins; 312 } 313 314 foreach ( $satellites as $satellite ) { 315 if ( ! empty( $satellite['origin'] ) ) { 316 $origins[] = $satellite['origin']; 317 } 318 } 319 320 return $origins; 321 } 322 323 /** 324 * Plugin activation 325 * 326 * @return void 327 */ 328 public function activate(): void { 329 Cnvrse_Database::instance()->create_tables(); 330 flush_rewrite_rules(); 331 332 // Schedule cleanup. 333 if ( ! wp_next_scheduled( 'cnvrse_cleanup_transients' ) ) { 334 wp_schedule_event( time(), 'hourly', 'cnvrse_cleanup_transients' ); 335 } 336 } 337 338 /** 339 * Plugin deactivation 340 * 341 * @return void 342 */ 343 public function deactivate(): void { 344 wp_clear_scheduled_hook( 'cnvrse_auto_close_conversations' ); 345 wp_clear_scheduled_hook( 'cnvrse_check_inactive_visitors' ); 346 wp_clear_scheduled_hook( 'cnvrse_cleanup_transients' ); 347 flush_rewrite_rules(); 348 } 349 350 /** 351 * Init hook callback 352 * 353 * Note: Translations are loaded automatically by WordPress since version 4.6 354 * for plugins hosted on WordPress.org. No manual load_plugin_textdomain() needed. 355 * 356 * @return void 357 */ 358 public function init(): void { 359 // Placeholder for future init tasks. Translations auto-loaded by WordPress 4.6+. 360 } 361 362 /** 363 * Plugins loaded callback 364 * 365 * @return void 366 */ 367 public function loaded(): void { 368 do_action( 'cnvrse_loaded' ); 369 } 370 371 /** 372 * Cleanup expired transient data 373 * 374 * @return void 375 */ 376 public function cleanup_expired_data(): void { 377 $storage = cnvrse_make( 'Cnvrse_Storage_Provider' ); 378 379 if ( method_exists( $storage, 'cleanup' ) ) { 380 $storage->cleanup(); 381 } 382 } 383 } 384 385 /** 386 * Get main Cnvrse instance 387 * 388 * @return Cnvrse 389 */ 390 function cnvrse() { 391 return Cnvrse::instance(); 392 } 393 394 // Initialize plugin. 395 cnvrse();
Note: See TracChangeset
for help on using the changeset viewer.