Plugin Directory

Changeset 3333214


Ignore:
Timestamp:
07/24/2025 01:01:52 AM (7 months ago)
Author:
nexlifycreator
Message:

fix: Email provider validation and false success message prevention (v1.0.4)

Location:
nexlifydesk
Files:
346 added
12 edited

Legend:

Unmodified
Added
Removed
  • nexlifydesk/trunk/assets/js/nexlifydesk.js

    r3333095 r3333214  
    11491149
    11501150            var ajaxurl = (typeof nexlifydesk_vars !== 'undefined' && nexlifydesk_vars.ajaxurl) ? nexlifydesk_vars.ajaxurl : (typeof ajaxurl !== 'undefined' ? ajaxurl : window.ajaxurl);
    1151             var nonce = (typeof nexlifydesk_vars !== 'undefined' && nexlifydesk_vars.purge_nonce) ? nexlifydesk_vars.purge_nonce : '';
     1151            var nonce = (typeof nexlifydesk_admin_vars !== 'undefined' && nexlifydesk_admin_vars.nonce) ? nexlifydesk_admin_vars.nonce : '';
    11521152
    11531153            if (confirm('Are you sure you want to purge old data? This action cannot be undone.')) {
     
    19171917    });
    19181918});
     1919
     1920function nexlifydesk_update_templates() {
     1921    if (typeof nexlifydesk_admin_vars === 'undefined') {
     1922        alert('Configuration error. Please refresh the page.');
     1923        return;
     1924    }
     1925   
     1926    if (confirm(nexlifydesk_admin_vars.template_update_confirm)) {
     1927        var xhr = new XMLHttpRequest();
     1928        xhr.open('POST', nexlifydesk_admin_vars.ajaxurl, true);
     1929        xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
     1930        xhr.onreadystatechange = function() {
     1931            if (xhr.readyState === 4) {
     1932                if (xhr.status === 200) {
     1933                    document.getElementById('nexlifydesk-template-update-notice').style.display = 'none';
     1934                    alert(nexlifydesk_admin_vars.template_update_success);
     1935                } else {
     1936                    alert(nexlifydesk_admin_vars.template_update_error);
     1937                }
     1938            }
     1939        };
     1940        xhr.send('action=nexlifydesk_update_email_templates&nonce=' + nexlifydesk_admin_vars.template_update_nonce);
     1941    }
     1942}
     1943
     1944function nexlifydesk_dismiss_notice() {
     1945    if (typeof nexlifydesk_admin_vars === 'undefined') {
     1946        document.getElementById('nexlifydesk-template-update-notice').style.display = 'none';
     1947        return;
     1948    }
     1949   
     1950    var xhr = new XMLHttpRequest();
     1951    xhr.open('POST', nexlifydesk_admin_vars.ajaxurl, true);
     1952    xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
     1953    xhr.onreadystatechange = function() {
     1954        if (xhr.readyState === 4) {
     1955            document.getElementById('nexlifydesk-template-update-notice').style.display = 'none';
     1956        }
     1957    };
     1958    xhr.send('action=nexlifydesk_dismiss_template_notice&nonce=' + nexlifydesk_admin_vars.template_dismiss_nonce);
     1959}
     1960
     1961// Make functions globally available
     1962window.nexlifydesk_update_templates = nexlifydesk_update_templates;
     1963window.nexlifydesk_dismiss_notice = nexlifydesk_dismiss_notice;
  • nexlifydesk/trunk/email-source/nexlifydesk-email-pipe.php

    r3333095 r3333214  
    5252
    5353/**
    54  * Get ticket by ticket ID
     54 * Gets ticket by ticket_id string (e.g., "T1102")
     55 *
    5556 * @param string $ticket_id The ticket ID string
    5657 * @return object|null The ticket object if found, null otherwise
     
    6162
    6263/**
    63  *
     64 * Clean email subject for better duplicate detection
    6465 * Removes Re:, Fwd:, etc. prefixes and normalizes the subject
    6566 *
     
    9091    $table_name = $wpdb->prefix . 'nexlifydesk_tickets';
    9192
     93    // For non-registered users (user_id = 0)
    9294    if ($user_id == 0 && !empty($email)) {
    9395       
     
    162164
    163165function nexlifydesk_fetch_custom_emails() {
     166    // Check if IMAP extension is available
    164167    if (!extension_loaded('imap')) {
    165168        add_action('admin_notices', function() {
     
    170173            }
    171174        });
    172         return;
     175        return array('error' => 'IMAP extension is not available on this server.');
    173176    }
    174177
    175178    $settings = get_option('nexlifydesk_imap_settings', array());
    176179
    177     $host = $settings['host'];
    178     $port = $settings['port'];
    179     $encryption = $settings['encryption'];
    180     $username = $settings['username'];
     180    $host = $settings['host'] ?? '';
     181    $port = $settings['port'] ?? '';
     182    $encryption = $settings['encryption'] ?? '';
     183    $username = $settings['username'] ?? '';
    181184    $password = nexlifydesk_get_safe_password($settings['password'] ?? '');
    182185    $protocol = isset($settings['protocol']) ? $settings['protocol'] : 'imap';
    183186    $delete_emails = isset($settings['delete_emails_after_fetch']) ? $settings['delete_emails_after_fetch'] : 1;
     187   
     188    // Validate required settings
     189    if (empty($host) || empty($port) || empty($username) || empty($password)) {
     190        return array('error' => 'Custom IMAP/POP3 credentials not configured. Please configure Host, Port, Username, and Password.');
     191    }
     192
     193    // Initialize tracking variables
     194    $tickets_created = 0;
     195    $replies_added = 0;
     196    $emails_processed = 0;
    184197
    185198    if ($protocol === 'imap') {
    186         $mailbox = "{" . $host . ":" . $port . "/imap/" . $encryption . "}INBOX";
    187        
    188         $inbox = @imap_open($mailbox, $username, $password);
    189         if (!$inbox) {
    190             return;
    191         }
    192         $emails = imap_search($inbox, 'UNSEEN');
    193         if ($emails) {
    194             foreach ($emails as $email_number) {
    195                 $overview = imap_fetch_overview($inbox, $email_number, 0)[0];
    196                
    197                 $message = nexlifydesk_extract_email_body($inbox, $email_number);
    198                 $subject = isset($overview->subject) ? $overview->subject : '';
    199                 $from = isset($overview->from) ? $overview->from : '';
    200                 $date = isset($overview->date) ? $overview->date : '';
    201                
    202                 if (function_exists('nexlifydesk_decode_email_subject')) {
    203                     $subject = nexlifydesk_decode_email_subject($subject);
    204                 } else {
    205                     $subject = nexlifydesk_decode_email_content($subject);
    206                 }
    207                 $from = nexlifydesk_decode_email_content($from);
    208                
    209                 $parsed_from = function_exists('nexlifydesk_parse_email_from') ?
    210                     nexlifydesk_parse_email_from($from) : ['name' => '', 'email' => $from];
    211                
    212                 $email_address = $parsed_from['email'];
    213                 $sender_name = $parsed_from['name'];
    214 
    215                 $is_admin_or_agent = function_exists('nexlifydesk_is_admin_or_agent_email') && nexlifydesk_is_admin_or_agent_email($email_address);
    216                
    217                 $original_length = strlen($message);
    218                 if ($is_admin_or_agent) {
    219                     // Keep original message intact for admin/agent
    220                 } else {
    221                     if (function_exists('nexlifydesk_extract_clean_email_content')) {
    222                         $message = nexlifydesk_extract_clean_email_content($message, $email_address);
    223                     } else {
    224                         $message = nexlifydesk_strip_email_thread($message, $email_address);
    225                     }
    226                 }
    227                 $stripped_length = strlen($message);
    228                
    229                 if (!$is_admin_or_agent && $stripped_length < ($original_length * 0.1) && $original_length > 100) {
    230                     if (function_exists('nexlifydesk_strip_email_thread')) {
    231                         $fallback_message = nexlifydesk_strip_email_thread($message, $email_address);
    232                         if (strlen($fallback_message) > $stripped_length) {
    233                             $message = $fallback_message;
    234                         }
    235                     }
    236                 }
    237                
    238                 if (empty($email_address)) {
    239                     $email_address = $from;
    240                     if (preg_match('/<(.+?)>/', $from, $matches)) {
    241                         $email_address = $matches[1];
    242                     }
    243                 }
    244                
    245                 if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) {
    246                     imap_setflag_full($inbox, $email_number, "\\Seen");
    247                     continue;
    248                 }
    249 
    250                 if (function_exists('nexlifydesk_should_block_email') &&
    251                     nexlifydesk_should_block_email($email_address, $subject, $message, $settings)) {
    252                     imap_setflag_full($inbox, $email_number, "\\Seen");
    253                     if ($delete_emails) {
    254                         imap_delete($inbox, $email_number);
    255                     }
    256                     continue;
    257                 }
    258 
    259                 $user = get_user_by('email', $email_address);
    260                 $user_id = $user ? $user->ID : 0;
    261 
    262                 if (class_exists('NexlifyDesk_Rate_Limiter') && NexlifyDesk_Rate_Limiter::is_rate_limited($user_id, $email_address)) {
    263                    
    264                     if ($delete_emails) {
    265                         imap_delete($inbox, $email_number);
    266                     }
    267                     continue;
    268                 }
    269 
    270                 $ticket_id_from_subject = function_exists('nexlifydesk_extract_ticket_id_from_subject') ? nexlifydesk_extract_ticket_id_from_subject($subject) : null;
    271                 $existing_ticket = null;
    272                
    273                 if ($ticket_id_from_subject && function_exists('nexlifydesk_get_ticket_by_ticket_id')) {
    274                     $existing_ticket = nexlifydesk_get_ticket_by_ticket_id($ticket_id_from_subject);
    275                 }
    276                
    277                 if (!$existing_ticket) {
    278                     $cleaned_subject = nexlifydesk_clean_email_subject($subject);
    279                    
    280                     if (function_exists('nexlifydesk_check_email_duplicate')) {
    281                         $duplicate_data = array(
    282                             'user_id' => $user_id,
    283                             'subject' => $cleaned_subject,
    284                             'message' => $message,
    285                             'email' => $email_address,
    286                             'source' => 'email'
    287                         );
    288                         $existing_ticket = nexlifydesk_check_email_duplicate($duplicate_data);
    289                     } else {
    290                         $ticket_data = array(
    291                             'user_id' => $user_id,
    292                             'subject' => $cleaned_subject,
    293                             'message' => $message,
    294                             'email' => $email_address,
    295                             'source' => 'email'
    296                         );
    297                         $existing_ticket = NexlifyDesk_Tickets::check_for_duplicate_ticket($ticket_data);
    298                     }
    299                 }
    300 
    301                 if ($existing_ticket) {
    302                     $reply_data = array(
    303                         'ticket_id' => $existing_ticket->id,
    304                         'user_id' => $user_id,
    305                         'message' => $message,
    306                         'source' => 'email'
    307                     );
    308                    
    309                     if (!$user_id && (!empty($sender_name) || !empty($email_address))) {
    310                         $sender_info = [];
    311                         if (!empty($sender_name)) {
    312                             $sender_info[] = "Name: {$sender_name}";
    313                         }
    314                         if (!empty($email_address)) {
    315                             $sender_info[] = "Email: {$email_address}";
    316                         }
    317                         if (!empty($sender_info)) {
    318                             $reply_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Reply]\n" . $message;
    319                         }
    320                     }
    321                    
    322                     NexlifyDesk_Tickets::add_reply($reply_data);
    323                 } else {
    324                     $cleaned_subject = nexlifydesk_clean_email_subject($subject);
    325                    
    326                     $ticket_data = array(
    327                         'user_id' => $user_id,
    328                         'subject' => $cleaned_subject,
    329                         'message' => $message,
    330                         'source' => 'email',
    331                         'email' => $email_address
    332                     );
    333                    
    334                     if (!$user_id && (!empty($sender_name) || !empty($email_address))) {
    335                         $sender_info = [];
    336                         if (!empty($sender_name)) {
    337                             $sender_info[] = "Name: {$sender_name}";
    338                         }
    339                         if (!empty($email_address)) {
    340                             $sender_info[] = "Email: {$email_address}";
    341                         }
    342                         if (!empty($sender_info)) {
    343                             $ticket_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Message]\n" . $message;
    344                         }
    345                     }
    346                    
    347                     $new_ticket_id = NexlifyDesk_Tickets::create_ticket($ticket_data);
    348                     if (!$user_id && !empty($email_address) && $new_ticket_id && !is_wp_error($new_ticket_id)) {
    349                         update_post_meta($new_ticket_id, 'customer_email', $email_address);
    350                     }
    351                 }
    352 
    353                 imap_setflag_full($inbox, $email_number, "\\Seen");
    354                 if ($delete_emails) {
    355                     imap_delete($inbox, $email_number);
    356                 }
    357             }
     199        return nexlifydesk_process_imap_emails($host, $port, $encryption, $username, $password, $delete_emails);
     200    } elseif ($protocol === 'pop3') {
     201        return nexlifydesk_process_pop3_emails($host, $port, $encryption, $username, $password, $delete_emails);
     202    } else {
     203        return array('error' => 'Unsupported protocol. Please use IMAP or POP3.');
     204    }
     205}
     206
     207function nexlifydesk_process_imap_emails($host, $port, $encryption, $username, $password, $delete_emails) {
     208    $mailbox = "{" . $host . ":" . $port . "/imap/" . $encryption . "}INBOX";
     209   
     210    $inbox = @imap_open($mailbox, $username, $password);
     211    if (!$inbox) {
     212        return array('error' => 'Failed to connect to IMAP server. Please check your credentials and server settings.');
     213    }
     214   
     215    $emails = imap_search($inbox, 'UNSEEN');
     216    if (!$emails) {
     217        imap_close($inbox);
     218        return array('success' => true, 'message' => 'No new emails found on IMAP server.', 'count' => 0);
     219    }
     220   
     221    $tickets_created = 0;
     222    $replies_added = 0;
     223    $emails_processed = count($emails);
     224   
     225    foreach ($emails as $email_number) {
     226        $overview = imap_fetch_overview($inbox, $email_number, 0)[0];
     227       
     228        $message = nexlifydesk_extract_email_body($inbox, $email_number);
     229        $subject = isset($overview->subject) ? $overview->subject : '';
     230        $from = isset($overview->from) ? $overview->from : '';
     231        $date = isset($overview->date) ? $overview->date : '';
     232       
     233        // Decode MIME-encoded subject first, then apply general email content decoding
     234        if (function_exists('nexlifydesk_decode_email_subject')) {
     235            $subject = nexlifydesk_decode_email_subject($subject);
     236        } else {
     237            $subject = nexlifydesk_decode_email_content($subject);
     238        }
     239        $from = nexlifydesk_decode_email_content($from);
     240       
     241        $parsed_from = function_exists('nexlifydesk_parse_email_from') ?
     242            nexlifydesk_parse_email_from($from) : ['name' => '', 'email' => $from];
     243       
     244        $email_address = $parsed_from['email'];
     245        $sender_name = $parsed_from['name'];
     246
     247        $is_admin_or_agent = function_exists('nexlifydesk_is_admin_or_agent_email') && nexlifydesk_is_admin_or_agent_email($email_address);
     248       
     249        $original_length = strlen($message);
     250        if ($is_admin_or_agent) {
     251            // Keep original message intact for admin/agent
     252        } else {
     253            if (function_exists('nexlifydesk_extract_clean_email_content')) {
     254                $message = nexlifydesk_extract_clean_email_content($message, $email_address);
     255            } else {
     256                $message = nexlifydesk_strip_email_thread($message, $email_address);
     257            }
     258        }
     259        $stripped_length = strlen($message);
     260       
     261        if (!$is_admin_or_agent && $stripped_length < ($original_length * 0.1) && $original_length > 100) {
     262            if (function_exists('nexlifydesk_strip_email_thread')) {
     263                $fallback_message = nexlifydesk_strip_email_thread($message, $email_address);
     264                if (strlen($fallback_message) > $stripped_length) {
     265                    $message = $fallback_message;
     266                }
     267            }
     268        }
     269       
     270        if (empty($email_address)) {
     271            $email_address = $from;
     272            if (preg_match('/<(.+?)>/', $from, $matches)) {
     273                $email_address = $matches[1];
     274            }
     275        }
     276       
     277        if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) {
     278            imap_setflag_full($inbox, $email_number, "\\Seen");
     279            continue;
     280        }
     281
     282        $settings = get_option('nexlifydesk_imap_settings', array());
     283        if (function_exists('nexlifydesk_should_block_email') &&
     284            nexlifydesk_should_block_email($email_address, $subject, $message, $settings)) {
     285            imap_setflag_full($inbox, $email_number, "\\Seen");
    358286            if ($delete_emails) {
    359                 imap_expunge($inbox);
    360             }
    361         }
    362         imap_close($inbox);
    363     } elseif ($protocol === 'pop3') {
    364         $mailbox = "{" . $host . ":" . $port . "/pop3/" . $encryption . "}INBOX";
    365        
    366         $inbox = @imap_open($mailbox, $username, $password);
    367         if (!$inbox) {
    368             return;
    369         }
    370         $num_msgs = imap_num_msg($inbox);
    371         for ($i = 1; $i <= $num_msgs; $i++) {
    372             $header = imap_headerinfo($inbox, $i);
    373             $overview = imap_fetch_overview($inbox, $i, 0)[0];
    374            
    375             $message = nexlifydesk_extract_email_body($inbox, $i);
    376             $subject = isset($overview->subject) ? $overview->subject : (isset($header->subject) ? $header->subject : '');
    377             $from = isset($overview->from) ? $overview->from : (isset($header->from) ? $header->from : '');
    378             $date = isset($overview->date) ? $overview->date : (isset($header->date) ? $header->date : '');
    379            
    380             if (function_exists('nexlifydesk_decode_email_subject')) {
    381                 $subject = nexlifydesk_decode_email_subject($subject);
    382             } else {
    383                 $subject = nexlifydesk_decode_email_content($subject);
    384             }
    385             $from = nexlifydesk_decode_email_content($from);
    386            
    387             $parsed_from = function_exists('nexlifydesk_parse_email_from') ?
    388                 nexlifydesk_parse_email_from($from) : ['name' => '', 'email' => $from];
    389            
    390             $email_address = $parsed_from['email'];
    391             $sender_name = $parsed_from['name'];
    392            
    393             $is_admin_or_agent = function_exists('nexlifydesk_is_admin_or_agent_email') && nexlifydesk_is_admin_or_agent_email($email_address);
    394            
    395             $original_length = strlen($message);
    396             if ($is_admin_or_agent) {
    397                 // Keep original message intact for admin/agent
    398             } else {
    399                 if (function_exists('nexlifydesk_extract_clean_email_content')) {
    400                     $message = nexlifydesk_extract_clean_email_content($message, $email_address);
    401                 } else {
    402                     $message = nexlifydesk_strip_email_thread($message, $email_address);
    403                 }
    404             }
    405             $stripped_length = strlen($message);
    406            
    407             if (!$is_admin_or_agent && $stripped_length < ($original_length * 0.1) && $original_length > 100) {
    408             }
    409            
    410             if (empty($email_address)) {
    411                 $email_address = $from;
    412                 if (preg_match('/<(.+?)>/', $from, $matches)) {
    413                     $email_address = $matches[1];
    414                 }
    415             }
    416            
    417             if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) {
    418                 if ($delete_emails) {
    419                     imap_delete($inbox, $i);
    420                 }
    421                 continue;
    422             }
    423 
    424             if (function_exists('nexlifydesk_should_block_email') &&
    425                 nexlifydesk_should_block_email($email_address, $subject, $message, $settings)) {
    426                 if ($delete_emails) {
    427                     imap_delete($inbox, $i);
    428                 }
    429                 continue;
    430             }
    431 
    432             $user = get_user_by('email', $email_address);
    433             $user_id = $user ? $user->ID : 0;
    434 
    435             if (class_exists('NexlifyDesk_Rate_Limiter') && NexlifyDesk_Rate_Limiter::is_rate_limited($user_id, $email_address)) {
    436                
    437                 if ($delete_emails) {
    438                     imap_delete($inbox, $i);
    439                 }
    440                 continue;
    441             }
    442 
    443             $ticket_id_from_subject = function_exists('nexlifydesk_extract_ticket_id_from_subject') ? nexlifydesk_extract_ticket_id_from_subject($subject) : null;
    444             $existing_ticket = null;
    445            
    446             if ($ticket_id_from_subject && function_exists('nexlifydesk_get_ticket_by_ticket_id')) {
    447                 $existing_ticket = nexlifydesk_get_ticket_by_ticket_id($ticket_id_from_subject);
    448             }
    449            
    450             if (!$existing_ticket) {
    451                 $cleaned_subject = nexlifydesk_clean_email_subject($subject);
    452                
    453                 if (function_exists('nexlifydesk_check_email_duplicate')) {
    454                     $duplicate_data = array(
    455                         'user_id' => $user_id,
    456                         'subject' => $cleaned_subject,
    457                         'message' => $message,
    458                         'email' => $email_address,
    459                         'source' => 'email'
    460                     );
    461                     $existing_ticket = nexlifydesk_check_email_duplicate($duplicate_data);
    462                 } else {
    463                     $ticket_data = array(
    464                         'user_id' => $user_id,
    465                         'subject' => $cleaned_subject,
    466                         'message' => $message,
    467                         'email' => $email_address,
    468                         'source' => 'email'
    469                     );
    470                     $existing_ticket = NexlifyDesk_Tickets::check_for_duplicate_ticket($ticket_data);
    471                 }
    472             }
    473 
    474             if ($existing_ticket) {
    475                 $reply_data = array(
    476                     'ticket_id' => $existing_ticket->id,
     287                imap_delete($inbox, $email_number);
     288            }
     289            continue;
     290        }
     291
     292        $user = get_user_by('email', $email_address);
     293        $user_id = $user ? $user->ID : 0;
     294
     295        if (class_exists('NexlifyDesk_Rate_Limiter') && NexlifyDesk_Rate_Limiter::is_rate_limited($user_id, $email_address)) {
     296           
     297            if ($delete_emails) {
     298                imap_delete($inbox, $email_number);
     299            }
     300            continue;
     301        }
     302
     303        $ticket_id_from_subject = function_exists('nexlifydesk_extract_ticket_id_from_subject') ? nexlifydesk_extract_ticket_id_from_subject($subject) : null;
     304        $existing_ticket = null;
     305       
     306        if ($ticket_id_from_subject && function_exists('nexlifydesk_get_ticket_by_ticket_id')) {
     307            $existing_ticket = nexlifydesk_get_ticket_by_ticket_id($ticket_id_from_subject);
     308        }
     309       
     310        if (!$existing_ticket) {
     311            $cleaned_subject = nexlifydesk_clean_email_subject($subject);
     312           
     313            if (function_exists('nexlifydesk_check_email_duplicate')) {
     314                $duplicate_data = array(
    477315                    'user_id' => $user_id,
     316                    'subject' => $cleaned_subject,
    478317                    'message' => $message,
     318                    'email' => $email_address,
    479319                    'source' => 'email'
    480320                );
    481                
    482                 if (!$user_id && (!empty($sender_name) || !empty($email_address))) {
    483                     $sender_info = [];
    484                     if (!empty($sender_name)) {
    485                         $sender_info[] = "Name: {$sender_name}";
    486                     }
    487                     if (!empty($email_address)) {
    488                         $sender_info[] = "Email: {$email_address}";
    489                     }
    490                     if (!empty($sender_info)) {
    491                         $reply_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Reply]\n" . $message;
    492                     }
    493                 }
    494                
    495                 NexlifyDesk_Tickets::add_reply($reply_data);
     321                $existing_ticket = nexlifydesk_check_email_duplicate($duplicate_data);
    496322            } else {
    497                 $cleaned_subject = nexlifydesk_clean_email_subject($subject);
    498                
    499323                $ticket_data = array(
    500324                    'user_id' => $user_id,
    501325                    'subject' => $cleaned_subject,
    502326                    'message' => $message,
    503                     'source' => 'email',
    504                     'email' => $email_address
     327                    'email' => $email_address,
     328                    'source' => 'email'
    505329                );
    506                
    507                 if (!$user_id && (!empty($sender_name) || !empty($email_address))) {
    508                     $sender_info = [];
    509                     if (!empty($sender_name)) {
    510                         $sender_info[] = "Name: {$sender_name}";
    511                     }
    512                     if (!empty($email_address)) {
    513                         $sender_info[] = "Email: {$email_address}";
    514                     }
    515                     if (!empty($sender_info)) {
    516                         $ticket_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Message]\n" . $message;
    517                     }
    518                 }
    519                
    520                 NexlifyDesk_Tickets::create_ticket($ticket_data);
    521             }
     330                $existing_ticket = NexlifyDesk_Tickets::check_for_duplicate_ticket($ticket_data);
     331            }
     332        }
     333
     334        if ($existing_ticket) {
     335            $reply_data = array(
     336                'ticket_id' => $existing_ticket->id,
     337                'user_id' => $user_id,
     338                'message' => $message,
     339                'source' => 'email'
     340            );
     341           
     342            if (!$user_id && (!empty($sender_name) || !empty($email_address))) {
     343                $sender_info = [];
     344                if (!empty($sender_name)) {
     345                    $sender_info[] = "Name: {$sender_name}";
     346                }
     347                if (!empty($email_address)) {
     348                    $sender_info[] = "Email: {$email_address}";
     349                }
     350                if (!empty($sender_info)) {
     351                    $reply_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Reply]\n" . $message;
     352                }
     353            }
     354           
     355            NexlifyDesk_Tickets::add_reply($reply_data);
     356            $replies_added++;
     357        } else {
     358            $cleaned_subject = nexlifydesk_clean_email_subject($subject);
     359           
     360            $ticket_data = array(
     361                'user_id' => $user_id,
     362                'subject' => $cleaned_subject,
     363                'message' => $message,
     364                'source' => 'email',
     365                'email' => $email_address
     366            );
     367           
     368            if (!$user_id && (!empty($sender_name) || !empty($email_address))) {
     369                $sender_info = [];
     370                if (!empty($sender_name)) {
     371                    $sender_info[] = "Name: {$sender_name}";
     372                }
     373                if (!empty($email_address)) {
     374                    $sender_info[] = "Email: {$email_address}";
     375                }
     376                if (!empty($sender_info)) {
     377                    $ticket_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Message]\n" . $message;
     378                }
     379            }
     380           
     381            $new_ticket_id = NexlifyDesk_Tickets::create_ticket($ticket_data);
     382            if ($new_ticket_id && !is_wp_error($new_ticket_id)) {
     383                $tickets_created++;
     384            }
     385            if (!$user_id && !empty($email_address) && $new_ticket_id && !is_wp_error($new_ticket_id)) {
     386                update_post_meta($new_ticket_id, 'customer_email', $email_address);
     387            }
     388        }
     389
     390        imap_setflag_full($inbox, $email_number, "\\Seen");
     391        if ($delete_emails) {
     392            imap_delete($inbox, $email_number);
     393        }
     394    }
     395   
     396    if ($delete_emails) {
     397        imap_expunge($inbox);
     398    }
     399   
     400    imap_close($inbox);
     401   
     402    return array(
     403        'success' => true,
     404        'message' => "Successfully processed {$emails_processed} emails. Created {$tickets_created} tickets and added {$replies_added} replies.",
     405        'tickets_created' => $tickets_created,
     406        'replies_added' => $replies_added,
     407        'emails_processed' => $emails_processed
     408    );
     409}
     410
     411function nexlifydesk_process_pop3_emails($host, $port, $encryption, $username, $password, $delete_emails) {
     412    $mailbox = "{" . $host . ":" . $port . "/pop3/" . $encryption . "}INBOX";
     413   
     414    $inbox = @imap_open($mailbox, $username, $password);
     415    if (!$inbox) {
     416        return array('error' => 'Failed to connect to POP3 server. Please check your credentials and server settings.');
     417    }
     418   
     419    $num_msgs = imap_num_msg($inbox);
     420    $tickets_created = 0;
     421    $replies_added = 0;
     422    $emails_processed = $num_msgs;
     423   
     424    for ($i = 1; $i <= $num_msgs; $i++) {
     425        $header = imap_headerinfo($inbox, $i);
     426        $overview = imap_fetch_overview($inbox, $i, 0)[0];
     427       
     428        $message = nexlifydesk_extract_email_body($inbox, $i);
     429        $subject = isset($overview->subject) ? $overview->subject : (isset($header->subject) ? $header->subject : '');
     430        $from = isset($overview->from) ? $overview->from : (isset($header->from) ? $header->from : '');
     431        $date = isset($overview->date) ? $overview->date : (isset($header->date) ? $header->date : '');
     432       
     433        if (function_exists('nexlifydesk_decode_email_subject')) {
     434            $subject = nexlifydesk_decode_email_subject($subject);
     435        } else {
     436            $subject = nexlifydesk_decode_email_content($subject);
     437        }
     438        $from = nexlifydesk_decode_email_content($from);
     439       
     440        $parsed_from = function_exists('nexlifydesk_parse_email_from') ?
     441            nexlifydesk_parse_email_from($from) : ['name' => '', 'email' => $from];
     442       
     443        $email_address = $parsed_from['email'];
     444        $sender_name = $parsed_from['name'];
     445       
     446        $is_admin_or_agent = function_exists('nexlifydesk_is_admin_or_agent_email') && nexlifydesk_is_admin_or_agent_email($email_address);
     447       
     448        $original_length = strlen($message);
     449        if ($is_admin_or_agent) {
     450            // Keep original message intact for admin/agent
     451        } else {
     452            if (function_exists('nexlifydesk_extract_clean_email_content')) {
     453                $message = nexlifydesk_extract_clean_email_content($message, $email_address);
     454            } else {
     455                $message = nexlifydesk_strip_email_thread($message, $email_address);
     456            }
     457        }
     458        $stripped_length = strlen($message);
     459       
     460        if (!$is_admin_or_agent && $stripped_length < ($original_length * 0.1) && $original_length > 100) {
     461            if (function_exists('nexlifydesk_strip_email_thread')) {
     462                $fallback_message = nexlifydesk_strip_email_thread($message, $email_address);
     463                if (strlen($fallback_message) > $stripped_length) {
     464                    $message = $fallback_message;
     465                }
     466            }
     467        }
     468       
     469        if (empty($email_address)) {
     470            $email_address = $from;
     471            if (preg_match('/<(.+?)>/', $from, $matches)) {
     472                $email_address = $matches[1];
     473            }
     474        }
     475       
     476        if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) {
    522477            if ($delete_emails) {
    523478                imap_delete($inbox, $i);
    524479            }
     480            continue;
     481        }
     482
     483        $settings = get_option('nexlifydesk_imap_settings', array());
     484        if (function_exists('nexlifydesk_should_block_email') &&
     485            nexlifydesk_should_block_email($email_address, $subject, $message, $settings)) {
     486            if ($delete_emails) {
     487                imap_delete($inbox, $i);
     488            }
     489            continue;
     490        }
     491
     492        $user = get_user_by('email', $email_address);
     493        $user_id = $user ? $user->ID : 0;
     494
     495        if (class_exists('NexlifyDesk_Rate_Limiter') && NexlifyDesk_Rate_Limiter::is_rate_limited($user_id, $email_address)) {
     496           
     497            if ($delete_emails) {
     498                imap_delete($inbox, $i);
     499            }
     500            continue;
     501        }
     502
     503        $ticket_id_from_subject = function_exists('nexlifydesk_extract_ticket_id_from_subject') ? nexlifydesk_extract_ticket_id_from_subject($subject) : null;
     504        $existing_ticket = null;
     505       
     506        if ($ticket_id_from_subject && function_exists('nexlifydesk_get_ticket_by_ticket_id')) {
     507            $existing_ticket = nexlifydesk_get_ticket_by_ticket_id($ticket_id_from_subject);
     508        }
     509       
     510        if (!$existing_ticket) {
     511            $cleaned_subject = nexlifydesk_clean_email_subject($subject);
     512           
     513            if (function_exists('nexlifydesk_check_email_duplicate')) {
     514                $duplicate_data = array(
     515                    'user_id' => $user_id,
     516                    'subject' => $cleaned_subject,
     517                    'message' => $message,
     518                    'email' => $email_address,
     519                    'source' => 'email'
     520                );
     521                $existing_ticket = nexlifydesk_check_email_duplicate($duplicate_data);
     522            } else {
     523                $ticket_data = array(
     524                    'user_id' => $user_id,
     525                    'subject' => $cleaned_subject,
     526                    'message' => $message,
     527                    'email' => $email_address,
     528                    'source' => 'email'
     529                );
     530                $existing_ticket = NexlifyDesk_Tickets::check_for_duplicate_ticket($ticket_data);
     531            }
     532        }
     533
     534        if ($existing_ticket) {
     535            $reply_data = array(
     536                'ticket_id' => $existing_ticket->id,
     537                'user_id' => $user_id,
     538                'message' => $message,
     539                'source' => 'email'
     540            );
     541           
     542            if (!$user_id && (!empty($sender_name) || !empty($email_address))) {
     543                $sender_info = [];
     544                if (!empty($sender_name)) {
     545                    $sender_info[] = "Name: {$sender_name}";
     546                }
     547                if (!empty($email_address)) {
     548                    $sender_info[] = "Email: {$email_address}";
     549                }
     550                if (!empty($sender_info)) {
     551                    $reply_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Reply]\n" . $message;
     552                }
     553            }
     554           
     555            NexlifyDesk_Tickets::add_reply($reply_data);
     556            $replies_added++;
     557        } else {
     558            $cleaned_subject = nexlifydesk_clean_email_subject($subject);
     559           
     560            $ticket_data = array(
     561                'user_id' => $user_id,
     562                'subject' => $cleaned_subject,
     563                'message' => $message,
     564                'source' => 'email',
     565                'email' => $email_address
     566            );
     567           
     568            if (!$user_id && (!empty($sender_name) || !empty($email_address))) {
     569                $sender_info = [];
     570                if (!empty($sender_name)) {
     571                    $sender_info[] = "Name: {$sender_name}";
     572                }
     573                if (!empty($email_address)) {
     574                    $sender_info[] = "Email: {$email_address}";
     575                }
     576                if (!empty($sender_info)) {
     577                    $ticket_data['message'] = "[Customer Details]\n" . implode("\n", $sender_info) . "\n\n[Message]\n" . $message;
     578                }
     579            }
     580           
     581            $new_ticket_id = NexlifyDesk_Tickets::create_ticket($ticket_data);
     582            if ($new_ticket_id && !is_wp_error($new_ticket_id)) {
     583                $tickets_created++;
     584            }
    525585        }
    526586        if ($delete_emails) {
    527             imap_expunge($inbox);
    528         }
    529         imap_close($inbox);
    530     }
     587            imap_delete($inbox, $i);
     588        }
     589    }
     590   
     591    if ($delete_emails) {
     592        imap_expunge($inbox);
     593    }
     594   
     595    imap_close($inbox);
     596   
     597    return array(
     598        'success' => true,
     599        'message' => "Successfully processed {$emails_processed} emails. Created {$tickets_created} tickets and added {$replies_added} replies.",
     600        'tickets_created' => $tickets_created,
     601        'replies_added' => $replies_added,
     602        'emails_processed' => $emails_processed
     603    );
    531604}
    532605
     
    671744    }
    672745   
    673     // Only proceed if password is properly encrypted
    674746    if (nexlifydesk_is_encrypted($encrypted_password)) {
    675747        $decrypted = nexlifydesk_decrypt($encrypted_password);
     
    678750        }
    679751    }
    680     // If not encrypted or decryption fails, return empty string
     752   
    681753    return '';
    682754}
     
    715787                        $message = $body;
    716788                        $is_html = false;
    717                         break; // Prefer plain text over HTML
     789                        break;
    718790                    } elseif (strtoupper($part->subtype) == 'HTML' && empty($message)) {
    719791                        $message = $body;
     
    809881 */
    810882function nexlifydesk_is_html_content($content) {
    811     // Check for common HTML patterns
    812883    $html_patterns = [
    813884        '/<html[^>]*>/i',
  • nexlifydesk/trunk/email-source/providers/aws-ses/aws-handler.php

    r3333095 r3333214  
    33if (!defined('ABSPATH')) exit;
    44
    5 // Ensure helpers are available for email decoding functions
    65if (!function_exists('nexlifydesk_decode_email_subject')) {
    76    require_once dirname(__FILE__) . '/../../includes/helpers.php';
    87}
    9 
    10 /**
    11  * AWS SES/WorkMail Email Handler for NexlifyDesk
    12  * Handles both IMAP email fetching and SES authentication
    13  */
    148
    159/**
     
    1711 */
    1812function nexlifydesk_fetch_aws_emails() {
    19     // Check if IMAP extension is available with better detection
    2013    if (!extension_loaded('imap') || !function_exists('imap_open')) {
    2114       
     
    3932            (isset($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on') ||
    4033            (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https') ||
    41             ((wp_parse_url(home_url(), PHP_URL_SCHEME) === 'https'))
     34            ((wp_parse_url(home_url(), ) === 'https'))
    4235        );
    4336   
     
    6356   
    6457    if (empty($organization_id) || empty($email) || empty($password)) {
    65         return;
     58        return array('error' => 'AWS WorkMail credentials not configured. Please configure Organization ID, Email, and Password.');
    6659    }
    6760   
     
    7265    $mailbox = "{{$imap_host}:{$imap_port}/imap/{$encryption}}INBOX";
    7366   
    74     // Clear any previous IMAP errors
    7567    if (function_exists('imap_errors')) {
    7668        imap_errors();
     
    8274    $inbox = @imap_open($mailbox, $email, $password, OP_SILENT);
    8375    if (!$inbox) {
    84         return;
    85     }
    86    
    87     $mailbox_info = imap_mailboxmsginfo($inbox);
    88     if ($mailbox_info) {
    89     } else {
    90         $total_msgs = imap_num_msg($inbox);
     76        return array('error' => 'Failed to connect to AWS WorkMail IMAP server. Please check your credentials and region.');
    9177    }
    9278   
     
    10490    $emails = imap_search($inbox, $search_criteria);
    10591   
    106     // If no emails found with date criteria, try just UNSEEN to see if there are any unread emails at all
    10792    if (!$emails) {
    108         $emails_unseen_only = imap_search($inbox, "UNSEEN");
    109         if ($emails_unseen_only) {
    110             // For now, let's process all unseen emails to solve the immediate issue
    111             $emails = $emails_unseen_only;
    112         } else {
    113             // IMAP search is failing, let's manually check all messages
     93        $emails = imap_search($inbox, "UNSEEN");
     94       
     95        if (!$emails) {
    11496            $total_msgs = imap_num_msg($inbox);
    115            
    11697            $manually_found = [];
    11798            for ($i = 1; $i <= $total_msgs; $i++) {
     
    119100                if (!empty($overview) && isset($overview[0])) {
    120101                    $msg = $overview[0];
    121                     // Check if message is unread (not seen)
    122102                    if (!isset($msg->seen) || $msg->seen == 0) {
    123103                        $manually_found[] = $i;
     
    125105                }
    126106            }
    127            
    128107            if (!empty($manually_found)) {
    129108                $emails = $manually_found;
     
    134113    if (!$emails) {
    135114        imap_close($inbox);
    136         return;
     115        return array('success' => true, 'message' => 'No new emails found in AWS WorkMail.', 'count' => 0);
    137116    }
    138117   
    139118    $processed_emails = get_option('nexlifydesk_aws_processed_emails', []);
    140119    $new_processed = [];
     120    $tickets_created = 0;
     121    $replies_added = 0;
     122    $emails_processed = 0;
    141123   
    142124    foreach ($emails as $email_number) {
     
    147129        }
    148130       
     131        $emails_processed++;
    149132        $new_processed[] = $uid;
    150133       
     
    155138        $subject = $overview->subject ?? '(No Subject)';
    156139       
    157         // Decode MIME-encoded subject to fix encoding issues like "=?UTF-8?Q?..."
    158140        if (function_exists('nexlifydesk_decode_email_subject')) {
    159141            $subject = nexlifydesk_decode_email_subject($subject);
     
    267249           
    268250            $reply_result = NexlifyDesk_Tickets::add_reply($reply_data);
     251            if ($reply_result) {
     252                $replies_added++;
     253            }
    269254        } else {
    270255            $cleaned_subject = nexlifydesk_clean_email_subject($subject);
     
    309294    }
    310295    update_option('nexlifydesk_aws_processed_emails', $all_processed);
     296   
     297    return array(
     298        'success' => true,
     299        'message' => sprintf('Processed %d emails. Created %d tickets, added %d replies.',
     300                           $emails_processed, $tickets_created, $replies_added),
     301        'count' => $emails_processed,
     302        'tickets_created' => $tickets_created,
     303        'replies_added' => $replies_added
     304    );
    311305}
    312306
     
    359353    }
    360354   
    361     // Check if the connection is ready for AWS SES
    362355    public function is_connection_ready() {
    363356        $is_ssl_enabled = $this->check_ssl_enabled();
  • nexlifydesk/trunk/email-source/providers/google/google-handler.php

    r3333095 r3333214  
    150150    $delete_emails = isset($settings['delete_emails_after_fetch']) ? $settings['delete_emails_after_fetch'] : 1;
    151151
     152    // Check if Google credentials are configured
     153    if (empty($settings['google_client_id']) || empty($settings['google_client_secret']) || empty($settings['google_refresh_token'])) {
     154        return array('error' => 'Google credentials not configured. Please authorize with Google first.');
     155    }
     156
    152157    $access_token = nexlifydesk_get_google_access_token();
    153158    if (!$access_token) {
    154         return;
     159        return array('error' => 'Failed to obtain Google access token. Please re-authorize with Google.');
    155160    }
    156161
     
    180185
    181186    if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
    182         return;
     187        return array('error' => 'Failed to connect to Gmail API. Please check your Google authorization.');
    183188    }
    184189
    185190    $list_body = json_decode(wp_remote_retrieve_body($response), true);
    186191    if (empty($list_body['messages'])) {
    187         return;
     192        return array('success' => true, 'message' => 'No new emails found in Gmail.', 'count' => 0);
    188193    }
    189194
    190195    $message_count = count($list_body['messages']);
     196    $tickets_created = 0;
     197    $replies_added = 0;
    191198
    192199    foreach ($list_body['messages'] as $message_item) {
     
    331338           
    332339            $reply_result = NexlifyDesk_Tickets::add_reply($reply_data);
     340            if ($reply_result) {
     341                $replies_added++;
     342            }
    333343        } else {
    334344            $cleaned_subject = nexlifydesk_clean_email_subject($subject_header);
     
    355365           
    356366            $ticket = NexlifyDesk_Tickets::create_ticket($ticket_data);
     367            if ($ticket && !is_wp_error($ticket)) {
     368                $tickets_created++;
     369            }
    357370        }
    358371       
     
    369382    }
    370383    update_option('nexlifydesk_processed_emails', $all_processed);
     384   
     385    return array(
     386        'success' => true,
     387        'message' => sprintf('Processed %d emails. Created %d tickets, added %d replies.',
     388                           $message_count, $tickets_created, $replies_added),
     389        'count' => $message_count,
     390        'tickets_created' => $tickets_created,
     391        'replies_added' => $replies_added
     392    );
    371393}
    372394
  • nexlifydesk/trunk/includes/class-nexlifydesk-admin.php

    r3333095 r3333214  
    864864                    'assigned_to_header' => __('Assigned To', 'nexlifydesk'),
    865865                    'agent_assigned_text' => __('Ticket assigned successfully!', 'nexlifydesk'),
     866                    'template_update_nonce' => wp_create_nonce('nexlifydesk_update_templates'),
     867                    'template_dismiss_nonce' => wp_create_nonce('nexlifydesk_dismiss_notice'),
     868                    'template_update_confirm' => __('This will clear your current email templates and use the new professional template files. You can customize them later in Email Templates settings. Continue?', 'nexlifydesk'),
     869                    'template_update_success' => __('Email templates updated successfully! Check the Email Templates page to see the changes.', 'nexlifydesk'),
     870                    'template_update_error' => __('Error updating templates. Please try again.', 'nexlifydesk'),
    866871                )
    867872            );
  • nexlifydesk/trunk/includes/class-nexlifydesk-ajax.php

    r3333095 r3333214  
    189189
    190190        $current_user = wp_get_current_user();
     191       
    191192        $can_reply = false;
    192193       
     
    986987                wp_send_json_error(array('message' => __('Invalid nonce.', 'nexlifydesk')));
    987988            }
    988             wp_send_json_success(array('message' => 'Custom email fetch test successful!'));
     989           
     990            // Call the actual custom email fetch function to test it
     991            $result = nexlifydesk_fetch_custom_emails();
     992           
     993            if (isset($result['error'])) {
     994                wp_send_json_error(array('message' => $result['error']));
     995            } else {
     996                wp_send_json_success(array('message' => $result['message'] ?? 'Custom email fetch test successful!'));
     997            }
    989998        }
    990999
     
    10411050            if (function_exists('nexlifydesk_fetch_emails')) {
    10421051                nexlifydesk_fetch_emails();
     1052               
     1053                if (function_exists('nexlifydesk_fetch_aws_emails')) {
     1054                    $result = nexlifydesk_fetch_aws_emails();
     1055                   
     1056                    if (is_array($result) && isset($result['error'])) {
     1057                        update_option('nexlifydesk_imap_settings', $current_settings);
     1058                        wp_send_json_error(array('message' => $result['error']));
     1059                        return;
     1060                    }
     1061                   
     1062                    if (is_array($result) && isset($result['success']) && $result['success']) {
     1063                        update_option('nexlifydesk_imap_settings', $current_settings);
     1064                        wp_send_json_success(array('message' => $result['message']));
     1065                        return;
     1066                    }
     1067                }
    10431068            } else {
    10441069                throw new Exception('Email fetch function not found');
     
    14711496
    14721497            if (function_exists('nexlifydesk_fetch_google_emails')) {
    1473                 nexlifydesk_fetch_google_emails();
     1498                $result = nexlifydesk_fetch_google_emails();
     1499               
     1500                // Check if there was an error
     1501                if (is_array($result) && isset($result['error'])) {
     1502                    wp_send_json_error(array('message' => $result['error']));
     1503                    return;
     1504                }
     1505               
     1506                // If we have a success result with details
     1507                if (is_array($result) && isset($result['success']) && $result['success']) {
     1508                    wp_send_json_success(array('message' => $result['message']));
     1509                    return;
     1510                }
    14741511            } else {
    14751512                throw new Exception('Google email fetch function not found');
    14761513            }
    14771514           
     1515            // Fallback for legacy behavior - check for recent tickets
    14781516            global $wpdb;
    14791517            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe and not user input
  • nexlifydesk/trunk/includes/class-nexlifydesk-database.php

    r3333095 r3333214  
    193193   
    194194    public static function check_and_run_migrations() {
    195         $current_version = get_option('nexlifydesk_db_version', '1.0.3');
     195        $current_version = get_option('nexlifydesk_db_version', '1.0.4');
    196196        $plugin_version = NEXLIFYDESK_VERSION;
    197197       
    198         if (version_compare($current_version, '1.0.3', '<')) {
     198        if (version_compare($current_version, '1.0.4', '<')) {
    199199            self::migrate_to_1_0_1();
    200             update_option('nexlifydesk_db_version', '1.0.3');
    201         }
    202        
    203         if (version_compare($current_version, '1.0.3', '<')) {
     200            update_option('nexlifydesk_db_version', '1.0.4');
     201        }
     202       
     203        if (version_compare($current_version, '1.0.4', '<')) {
    204204            self::migrate_to_1_0_2();
    205             update_option('nexlifydesk_db_version', '1.0.3');
     205            update_option('nexlifydesk_db_version', '1.0.4');
    206206        }
    207207       
     
    293293        global $wpdb;
    294294       
    295         $current_version = get_option('nexlifydesk_version', '1.0.3');
    296        
    297         if ($current_version === '1.0.3' && !get_option('nexlifydesk_db_version')) {
     295        $current_version = get_option('nexlifydesk_version', '1.0.4');
     296       
     297        if ($current_version === '1.0.4' && !get_option('nexlifydesk_db_version')) {
    298298            return;
    299299        }
  • nexlifydesk/trunk/includes/class-nexlifydesk-tickets.php

    r3333095 r3333214  
    606606        global $wpdb;
    607607       
    608         $allowed_statuses = array('open', 'pending', 'in_progress', 'resolved', 'closed');
     608        $allowed_statuses = array('open', 'pending', 'in_progress', 'resolved', 'closed'); // <-- Add in_progress here
    609609        if (!in_array($status, $allowed_statuses)) {
    610610            return false;
     
    16351635   
    16361636    /**
     1637     * Check for duplicate tickets using improved user-scoped logic:
     1638     * - For registered users: Check ONLY within their own tickets, never cross-user matching
     1639     * - For unregistered users: Consolidate by email address only, no content matching needed
     1640     *
    16371641     * @param array $data Ticket data to check for duplicates
    16381642     * @return object|false Returns existing ticket if duplicate found, false otherwise
    16391643     */
    16401644    public static function check_for_duplicate_ticket($data) {
     1645        // Use the new global duplicate detection function for all channels and user types
    16411646        if (!function_exists('nexlifydesk_find_duplicate_ticket')) {
    16421647            require_once dirname(__FILE__) . '/nexlifydesk-functions.php';
     
    17681773
    17691774    /**
     1775     * Update ticket priority
    17701776     *
    17711777     * @param int $ticket_id Ticket ID
  • nexlifydesk/trunk/includes/nexlifydesk-functions.php

    r3333095 r3333214  
    196196
    197197/**
    198  * - Registered users: Check any existing tickets for order numbers or subject matches
    199  * - If order numbers are found, check for existing tickets with matching order numbers
     198 * Global duplicate ticket detection for all channels
     199 * Implements correct logic per user requirements:
     200 * - Registered users: Check ONLY within their own tickets, never cross-user matching
    200201 * - Unregistered users: Consolidate by email address only, no content matching needed
    201202 *
     
    211212    $table_name = $wpdb->prefix . 'nexlifydesk_tickets';
    212213   
    213     // Registered users
     214    // ========================
     215    // A. REGISTERED USER LOGIC
     216    // ========================
    214217    if ($user_id > 0) {
     218        // Step 1: Check if this registered user has ANY existing ticket (open, pending, in_progress)
    215219        $cache_key = 'nexlifydesk_user_has_tickets_' . $user_id;
    216220        $user_has_tickets = wp_cache_get($cache_key);
     
    227231        }
    228232       
     233        // Step 2: If user has no existing tickets, create new ticket (no duplicate possible)
    229234        if (!$user_has_tickets) {
    230             return null;
    231         }
    232        
     235            return null; // No existing tickets = create new ticket
     236        }
     237       
     238        // Step 3: User has existing tickets - perform content-based duplicate detection ONLY within this user's tickets
    233239        $order_numbers = array();
    234240        if (!empty($subject)) {
     
    240246        $order_numbers = array_unique($order_numbers);
    241247       
     248        // Check for order number matches within user's own tickets
    242249        if (!empty($order_numbers)) {
    243250            $order_regex = implode('|', array_map('preg_quote', $order_numbers));
     
    257264        }
    258265       
     266        // Check for exact subject match within user's own tickets
    259267        if (!empty($subject)) {
    260268            $cache_key = 'nexlifydesk_user_subject_match_' . md5($user_id . $subject);
     
    273281        }
    274282       
     283        // Semantic similarity check within user's own tickets only
    275284        if (class_exists('TextAnalysis\\Comparisons\\CosineSimilarityComparison') && class_exists('TextAnalysis\\Tokenizers\\GeneralTokenizer')) {
    276             $similarity_threshold = 0.12;
     285            $similarity_threshold = 0.12; // Optimized threshold for better matching
    277286            $cache_key = 'nexlifydesk_user_tickets_semantic_' . $user_id;
    278287            $user_tickets = wp_cache_get($cache_key);
     
    289298           
    290299            if (!empty($user_tickets)) {
     300                // Manual keyword mapping for semantic matching
    291301                $keyword_map = array(
    292302                    'profile' => 'account', 'dashboard' => 'account', 'user' => 'account', 'panel' => 'account',
     
    323333                    );
    324334                    if ($similarity >= $similarity_threshold) {
    325                         return $ticket;
     335                        return $ticket; // Found duplicate within user's own tickets
    326336                    }
    327337                }
     
    329339        }
    330340       
     341        // No duplicate found within user's tickets = create new ticket for this user
    331342        return null;
    332343    }
    333344
     345    // ==========================
     346    // B. UNREGISTERED USER LOGIC
     347    // ==========================
    334348    if ($user_id == 0 && !empty($email)) {
    335349        // For unregistered users, we simply check if there's ANY existing ticket for this email
     350        // Content matching is NOT required - we want to consolidate all communication by email
    336351        $cache_key = 'nexlifydesk_email_consolidation_' . md5($email);
    337352        $existing_ticket = wp_cache_get($cache_key);
    338353
    339354        if (false === $existing_ticket) {
     355            // Find the most recent ticket for this email address (any status except closed)
    340356            // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is safe.
    341357            $existing_ticket = $wpdb->get_row( $wpdb->prepare(// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom table name is safe and required direct query.
     
    350366        }
    351367       
    352         return $existing_ticket;
     368        return $existing_ticket; // Return existing ticket for email consolidation, or null to create new
    353369    }
    354370   
     371    // No valid user_id or email = create new ticket
    355372    return null;
    356373}
  • nexlifydesk/trunk/nexlifydesk.php

    r3333095 r3333214  
    33 * Plugin Name: NexlifyDesk
    44 * Description: A modern, user-friendly support ticket system for WordPress with ticket submission, threaded replies, file attachments, agent assignment, and customizable.
    5  * Version: 1.0.3
     5 * Version: 1.0.4
    66 * Author URI: https://nexlifylabs.com
    77 * Supported Versions: 6.2+
     
    5555define('NEXLIFYDESK_PLUGIN_DIR', plugin_dir_path(__FILE__));
    5656define('NEXLIFYDESK_PLUGIN_URL', plugin_dir_url(__FILE__));
    57 define('NEXLIFYDESK_VERSION', '1.0.3');
     57define('NEXLIFYDESK_VERSION', '1.0.4');
    5858define('NEXLIFYDESK_TABLE_PREFIX', 'nexlifydesk_');
    5959define('NEXLIFYDESK_CAP_VIEW_ALL_TICKETS', 'nexlifydesk_view_all_tickets');
     
    9494}
    9595add_action('plugins_loaded', 'nexlifydesk_init');
     96
     97// Add plugin action links (donate button, etc.)
     98add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'nexlifydesk_plugin_action_links');
     99add_filter('plugin_row_meta', 'nexlifydesk_plugin_row_meta', 10, 2);
     100
     101function nexlifydesk_plugin_action_links($links) {
     102    $donate_link = '<a href="https://paypal.me/devteejay" target="_blank" style="color: #e74c3c; font-weight: bold;">❤️ ' . __('Donate', 'nexlifydesk') . '</a>';
     103    array_unshift($links, $donate_link);
     104    return $links;
     105}
     106
     107function nexlifydesk_plugin_row_meta($links, $file) {
     108    if (plugin_basename(__FILE__) === $file) {
     109        $row_meta = array(
     110            'support' => '<a href="' . esc_url('https://nexlifylabs.com/support') . '" target="_blank">' . __('Support', 'nexlifydesk') . '</a>',
     111            'docs' => '<a href="' . esc_url('https://nexlifylabs.com/nexlifydesk-documentation/') . '" target="_blank">' . __('Documentation', 'nexlifydesk') . '</a>',
     112            'donate' => '<a href="https://paypal.me/devteejay" target="_blank" style="color: #e74c3c; font-weight: bold;">❤️ ' . __('Support Development', 'nexlifydesk') . '</a>',
     113        );
     114        return array_merge($links, $row_meta);
     115    }
     116    return $links;
     117}
     118
     119// Add admin notice and action to update email templates for existing installations
    96120add_action('admin_notices', 'nexlifydesk_show_template_update_notice');
    97121add_action('wp_ajax_nexlifydesk_update_email_templates', 'nexlifydesk_ajax_update_email_templates');
     122add_action('wp_ajax_nexlifydesk_dismiss_template_notice', 'nexlifydesk_ajax_dismiss_template_notice');
    98123
    99124function nexlifydesk_show_template_update_notice() {
     
    103128    }
    104129   
     130    // Check if notice was dismissed
     131    if (get_option('nexlifydesk_template_notice_dismissed', false)) {
     132        return;
     133    }
     134   
    105135    $screen = get_current_screen();
    106136    if (!$screen || strpos($screen->id, 'nexlifydesk') === false) {
     
    108138    }
    109139   
     140    // Check if templates need updating (contain old placeholder format)
    110141    $existing_templates = get_option('nexlifydesk_email_templates', array());
    111142    $needs_update = false;
     
    127158            <p>
    128159                <strong><?php esc_html_e('NexlifyDesk Email Templates Update Available', 'nexlifydesk'); ?></strong><br>
    129                 <?php esc_html_e('Your email templates are using the old format. Click below to update them to use the new professional template files with better styling and functionality.', 'nexlifydesk'); ?>
     160                <?php esc_html_e('Your email templates are using the old format. Click below to update them to use the new template files with better styling and functionality.', 'nexlifydesk'); ?>
    130161            </p>
    131162            <p>
     
    138169            </p>
    139170        </div>
    140         <script>
    141         function nexlifydesk_update_templates() {
    142             if (confirm('<?php esc_html_e('This will clear your current email templates and use the new professional template files. You can customize them later in Email Templates settings. Continue?', 'nexlifydesk'); ?>')) {
    143                 var xhr = new XMLHttpRequest();
    144                 xhr.open('POST', ajaxurl, true);
    145                 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    146                 xhr.onreadystatechange = function() {
    147                     if (xhr.readyState === 4) {
    148                         if (xhr.status === 200) {
    149                             document.getElementById('nexlifydesk-template-update-notice').style.display = 'none';
    150                             alert('<?php esc_html_e('Email templates updated successfully! Check the Email Templates page to see the changes.', 'nexlifydesk'); ?>');
    151                         } else {
    152                             alert('<?php esc_html_e('Error updating templates. Please try again.', 'nexlifydesk'); ?>');
    153                         }
    154                     }
    155                 };
    156                 xhr.send('action=nexlifydesk_update_email_templates&nonce=<?php echo esc_attr( wp_create_nonce('nexlifydesk_update_templates') ); ?>');
    157             }
    158         }
    159        
    160         function nexlifydesk_dismiss_notice() {
    161             document.getElementById('nexlifydesk-template-update-notice').style.display = 'none';
    162         }
    163         </script>
    164171        <?php
    165172    }
     
    174181    }
    175182   
     183    // Clear existing templates to force use of template files
    176184    $templates = array(
    177185        'new_ticket' => '',
     
    187195}
    188196
     197function nexlifydesk_ajax_dismiss_template_notice() {
     198    // Verify nonce and permissions
     199    if (!current_user_can('manage_options') ||
     200        !isset($_POST['nonce']) ||
     201        !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'nexlifydesk_dismiss_notice')) {
     202        wp_die('Unauthorized');
     203    }
     204   
     205    // Set the dismissal flag
     206    update_option('nexlifydesk_template_notice_dismissed', true);
     207   
     208    wp_send_json_success(array('message' => __('Notice dismissed successfully!', 'nexlifydesk')));
     209}
     210
    189211// Activation function
    190212function nexlifydesk_activate() {
     
    201223    }
    202224
     225    // Load email templates from template files on activation
    203226    nexlifydesk_load_default_email_templates();
    204227}
     
    208231 */
    209232function nexlifydesk_load_default_email_templates() {
     233    // Only load if no templates exist yet (fresh installation)
    210234    $existing_templates = get_option('nexlifydesk_email_templates', array());
    211235   
     236    // Check if templates are empty or contain old placeholder-style templates
    212237    $needs_update = empty($existing_templates) ||
    213238                   (isset($existing_templates['new_reply']) &&
     
    215240   
    216241    if ($needs_update) {
     242        // Set placeholders indicating that template files are being used
    217243        $templates = array(
    218244            'new_ticket' => '',
     
    242268    wp_clear_scheduled_hook('nexlifydesk_auto_close_tickets');
    243269
     270    // Note: Email templates are now handled by template files in templates/emails/ directory
    244271}
    245272
     
    408435/**
    409436 * Freemius uninstall cleanup handler
     437 * This is called by Freemius when the plugin is uninstalled through their system
    410438 */
    411439function nexlifydesk_freemius_uninstall_cleanup() {
     440    // Include the uninstall script to handle cleanup
    412441    $uninstall_file = plugin_dir_path(__FILE__) . 'uninstall.php';
    413442    if (file_exists($uninstall_file)) {
     443        // Define the constant that uninstall.php expects
    414444        if (!defined('WP_UNINSTALL_PLUGIN')) {
    415445            define('WP_UNINSTALL_PLUGIN', true);
     
    419449}
    420450
     451// Register the uninstall hook to use the uninstall.php file
    421452register_uninstall_hook(__FILE__, 'nexlifydesk_freemius_uninstall_cleanup');
    422453
     
    462493        wp_send_json_error(__('You do not have permission to perform this action.', 'nexlifydesk'));
    463494    }
    464     check_ajax_referer('nexlifydesk_purge_data');
     495    check_ajax_referer('nexlifydesk-ajax-nonce', '_ajax_nonce');
    465496    global $wpdb;
    466497    $purged = array();
  • nexlifydesk/trunk/readme.txt

    r3333105 r3333214  
    44Requires at least: 6.2
    55Tested up to: 6.8
    6 Stable tag: 1.0.3
     6Stable tag: 1.0.4
    77License: GPLv2 or later
    88License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    349349== Changelog ==
    350350
     351= 1.0.4 =
     352- Enhanced email provider validation to prevent false success messages when credentials are not configured
     353- Fixed "Test Email Fetch" buttons now properly validate credentials before showing success/error status
     354- Improved AWS WorkMail connectivity across multiple server configurations and hosting environments
     355- Enhanced Google Workspace email integration with better error handling and credential validation
     356- Strengthened custom IMAP/POP3 email processing with comprehensive credential validation
     357- Improved JavaScript coding standards compliance by moving inline code to external files
     358- Enhanced AJAX handlers to check actual email fetch results instead of returning false positives
     359- Added proper return value validation for all email provider test functions
     360- Improved email piping reliability across different server setups and hosting providers
     361- Enhanced error messaging for better troubleshooting and configuration guidance
     362
    351363= 1.0.3 =
    352364- Fixed AWS Test Connection functionality for WorkMail and SES integration
     
    390402== Upgrade Notice ==
    391403
     404= 1.0.4 =
     405Important update: Enhanced email provider validation prevents false success messages, improved AWS connectivity across multiple server configurations, and strengthened JavaScript coding standards compliance.
     406
    392407= 1.0.3 =
    393 Major update: Fixes AWS connectivity issues, adds comprehensive system diagnostics, improves mobile UI responsiveness, introduces advanced duplicate detection with semantic text analysis, enhances production code quality, and provides comprehensive uninstall data removal options for better security and user control.
     408Major update: Fixes AWS connectivity issues, adds system diagnostics, improves mobile UI, introduces semantic duplicate detection, and enhances uninstall data security.
    394409
    395410= 1.0.2 =
  • nexlifydesk/trunk/uninstall.php

    r3333095 r3333214  
    2323    global $wpdb;
    2424
     25    // Get settings to check if data should be kept
    2526    $settings = get_option('nexlifydesk_settings', array());
    2627    $keep_data = isset($settings['keep_data_on_uninstall']) ? (bool)$settings['keep_data_on_uninstall'] : true;
    2728
     29    // Always remove scheduled hooks and transients (cleanup)
    2830    wp_clear_scheduled_hook('nexlifydesk_sla_check');
    2931    wp_clear_scheduled_hook('nexlifydesk_auto_close_tickets');
     
    3335    delete_transient('nexlifydesk_google_oauth_state');
    3436   
     37    // Clear any cached data
    3538    wp_cache_flush();
    3639
     40    // If user wants to keep data, only do minimal cleanup
    3741    if ($keep_data) {
     42        // Remove only version option but keep all user data
    3843        delete_option('nexlifydesk_db_version');
    3944        return;
    4045    }
    4146
     47    // User wants complete data removal - proceed with full cleanup
     48   
    4249    // Remove all plugin options and settings
    4350    $options_to_remove = array(
     
    5461    foreach ($options_to_remove as $option) {
    5562        delete_option($option);
     63        // Also remove from network options if multisite
    5664        if (is_multisite()) {
    5765            delete_network_option(null, $option);
     
    5967    }
    6068
     69    // Remove custom database tables
    6170    $table_names = array(
    6271        $wpdb->prefix . 'nexlifydesk_tickets',
     
    7180    }
    7281
     82    // Remove custom user roles
    7383    remove_role('nexlifydesk_agent');
    7484    remove_role('nexlifydesk_supervisor');
    7585
     86    // Remove custom capabilities from administrator role
    7687    $admin_role = get_role('administrator');
    7788    if ($admin_role) {
     
    89100    }
    90101
     102    // Remove uploaded files directory
    91103    $upload_dir = wp_upload_dir();
    92104    $plugin_upload_dir = trailingslashit($upload_dir['basedir']) . 'nexlifydesk/';
     
    96108    }
    97109
     110    // Remove any user meta related to the plugin
    98111    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Direct deletion is required for uninstall cleanup
    99112    $wpdb->query($wpdb->prepare(
     
    101114        'nexlifydesk_%'
    102115    ));
    103 
     116    // Optionally clear usermeta cache after deletion
    104117    wp_cache_flush();
    105118
     119    // Clean up any remaining cache entries and transients
    106120    $cache_keys_patterns = array(
    107121        'nexlifydesk_ticket_',
     
    114128    );
    115129   
     130    // Attempt to clean up cache entries (this is a best-effort cleanup)
    116131    global $wp_object_cache;
    117132    if (isset($wp_object_cache->cache)) {
     
    160175    }
    161176   
     177    // Use WP_Filesystem for directory removal
    162178    global $wp_filesystem;
    163179    if (empty($wp_filesystem)) {
Note: See TracChangeset for help on using the changeset viewer.