<?php

/*
 * ==========================================================
 * FUNCTIONS_MESSAGES.PHP
 * ==========================================================
 *
 * Messages functions file. © 2017-2025 board.support. All rights reserved.
 *
 * -----------------------------------------------------------
 * CONVERSATIONS
 * -----------------------------------------------------------
 *
 * 1. Return the user details of each conversation. This function is used internally by other functions.
 * 2. Return the messages grouped by conversation
 * 3. Return the conversations or messages older than the given date
 * 4. Return the conversations older than the given date of the user with the given ID
 * 5. Search conversations by searching user details and messages contents
 * 6. Search conversations of the user with the given ID
 * 7. Return the conversations of a user
 * 8. Return the ID of the last user conversation if any, otherwise create a new conversation and return its ID
 * 9. Return the messages of the a conversation older than the given date
 * 10. Return a conversation
 * 11. Create a new conversation and return the ID
 * 12. Update a conversation status with one of the allowed stutus:  live = 0, pending = 1, pending user = 2, archive = 3, trash = 4.
 * 13. Update the conversation department and alert the agents of that department
 * 14. Update the agent assigned to a conversation and alert the agent
 * 15. Send the updated conversation event
 * 16. Save a conversation as a CSV file
 * 17. Internal notes
 * 18. Direct message
 * 19. Return all agents that replied in a conversation
 * 20. Set or update the conversation opened by the agent in the admin area
 * 21. Check if a conversation is currently opened by an agent
 * 22. Count conversations
 * 23. Send all notifications types to all validated agents
 * 24. Check if the given conversation are assigned to a department or agent
 * 25. Return the ID of the last agent of a conversation
 * 26. Get the last message of a converation
 * 27. Delete conversation attachments
 * 28. Update the messages status
 * 29. Update the extra fields of a conversation
 *
 */

const SELECT_CONVERSATIONS = 'SELECT A.message, A.id AS `message_id`, A.attachments, A.payload, A.status_code AS `message_status_code`, A.creation_time AS `last_update_time`, B.id AS `message_user_id`, B.first_name AS `message_first_name`, B.last_name AS `message_last_name`, B.profile_image AS `message_profile_image`, B.user_type AS `message_user_type`, C.id AS `conversation_id`, C.user_id AS `conversation_user_id`, C.status_code AS `conversation_status_code`, C.creation_time AS `conversation_creation_time`, C.department, C.agent_id, C.title, C.source, C.extra, C.tags ';

function sb_get_conversations_users($conversations) {
    if (count($conversations) > 0) {
        $code = '(';
        for ($i = 0; $i < count($conversations); $i++) {
            $code .= sb_db_escape($conversations[$i]['conversation_id']) . ',';
        }
        $code = substr($code, 0, -1) . ')';
        $result = sb_db_get('SELECT sb_users.id, sb_users.first_name, sb_users.last_name, sb_users.profile_image, sb_users.user_type, sb_conversations.id AS `conversation_id` FROM sb_users, sb_conversations WHERE sb_users.id = sb_conversations.user_id AND sb_conversations.id IN ' . $code, false);
        for ($i = 0; $i < count($conversations); $i++) {
            $conversation_id = $conversations[$i]['conversation_id'];
            for ($j = 0; $j < count($result); $j++) {
                if ($conversation_id == $result[$j]['conversation_id']) {
                    $conversations[$i]['conversation_user_id'] = $result[$j]['id'];
                    $conversations[$i]['conversation_first_name'] = $result[$j]['first_name'];
                    $conversations[$i]['conversation_last_name'] = $result[$j]['last_name'];
                    $conversations[$i]['conversation_profile_image'] = $result[$j]['profile_image'];
                    $conversations[$i]['conversation_user_type'] = $result[$j]['user_type'];
                    break;
                }
            }
        }
    }
    return $conversations;
}

function sb_get_conversations($pagination = 0, $status_code = 0, $department = false, $source = false, $tag = false, $agent_id = false) {
    $exclude_visitors = '';
    if ($status_code == 3) {
        $ids = sb_db_get('SELECT A.id FROM sb_conversations A, sb_users B WHERE B.user_type <> "visitor" AND A.user_id = B.id', false);
        for ($i = 0; $i < count($ids); $i++) {
            $exclude_visitors .= $ids[$i]['id'] . ',';
        }
        if ($exclude_visitors) {
            $exclude_visitors = ' AND C.id IN (' . substr($exclude_visitors, 0, -1) . ')';
        }
    }
    if (!$pagination) {
        $pagination = 0;
    }
    if (!$status_code) {
        $status_code = 0;
    }
    sb_db_query('DROP TEMPORARY TABLE IF EXISTS sb_latest_messages');
    sb_db_query('CREATE TEMPORARY TABLE sb_latest_messages (id BIGINT PRIMARY KEY) ENGINE=MEMORY AS SELECT MAX(id) AS id FROM sb_messages GROUP BY conversation_id');
    $query = SELECT_CONVERSATIONS . 'FROM sb_latest_messages L JOIN sb_messages A ON A.id = L.id JOIN sb_users B ON B.id = A.user_id JOIN sb_conversations C ON C.id = A.conversation_id WHERE 1=1 ' . ($status_code === 'all' ? '' : ($status_code == 0 ? ' AND C.status_code NOT IN (3,4)' : ' AND C.status_code = ' . sb_db_escape($status_code))) . ($source !== false ? ' AND ' . ($source === '' || $source === 'chat' ? '(C.source IS NULL OR C.source = "")' : 'C.source = "' . sb_db_escape($source) . '"') : '') . ($tag ? ' AND C.tags LIKE "%' . sb_db_escape($tag) . '%"' : '') . ($agent_id ? ' AND C.agent_id = ' . sb_db_escape($agent_id, true) : '') . (sb_get_agent_department() === false && $department ? ' AND C.department = ' . sb_db_escape($department, true) : '') . sb_routing_and_department_db('C') . $exclude_visitors . ' ORDER BY ' . (sb_get_setting('order-by-date') ? '' : 'FIELD(C.status_code, 2) DESC,') . 'A.id DESC LIMIT ' . ($pagination * 100) . ', 100';
    $result = sb_db_get($query, false);
    foreach ($result as &$conversation) {
        if (empty($conversation['message']) && empty($conversation['attachments']) && !strpos($conversation['payload'], 'preview')) {
            $message = sb_db_get('SELECT * FROM sb_messages WHERE conversation_id = ' . $conversation['conversation_id'] . ' AND (message <> "" OR attachments <> "") ORDER BY id DESC LIMIT 1');
            if ($message) {
                $conversation['message'] = $message['message'];
                $conversation['attachments'] = $message['attachments'];
                $conversation['message_id'] = $message['id'];
                $conversation['message_user_id'] = $message['user_id'];
                $conversation['message_status_code'] = $message['status_code'];
                $conversation['payload'] = $message['payload'];
            }
        } else {
            $conversation['payload'] = json_decode($conversation['payload'], true);
        }
    }
    if (isset($result) && is_array($result)) {
        return sb_get_conversations_users($result);
    } else {
        return sb_error('db-error', 'sb_get_conversations', $result);
    }
}

function sb_get_new_conversations($datetime, $department = false, $source = false, $tag = false, $agent_id = false) {
    $datetime = sb_db_escape($datetime);
    $result = sb_db_get(SELECT_CONVERSATIONS . 'FROM sb_messages A, sb_users B, sb_conversations C WHERE A.id IN (SELECT max(id) FROM sb_messages WHERE ' . (is_numeric($datetime) ? ('id > ' . $datetime) : ('creation_time > "' . $datetime . '"')) . ' GROUP BY conversation_id) AND B.id = A.user_id AND C.id = A.conversation_id' . sb_routing_and_department_db('C') . ($source !== false ? ' AND ' . ($source === '' || $source === 'chat' ? '(C.source IS NULL OR C.source = "")' : 'C.source = "' . sb_db_escape($source) . '"') : '') . ($tag ? ' AND C.tags LIKE "%' . sb_db_escape($tag) . '%"' : '') . ($agent_id ? ' AND C.agent_id = ' . sb_db_escape($agent_id, true) : '') . ($department ? ' AND C.department = ' . sb_db_escape($department, true) : '') . ' GROUP BY conversation_id ORDER BY A.id DESC', false);
    if (isset($result) && is_array($result)) {
        return count($result) ? sb_get_conversations_users($result) : [];
    } else {
        return sb_error('db-error', 'sb_get_new_conversations', $result);
    }
}

function sb_get_new_user_conversations($user_id, $datetime) {
    $datetime = sb_db_escape($datetime);
    $user_id = sb_db_escape($user_id, true);
    return sb_db_get(SELECT_CONVERSATIONS . 'FROM sb_messages A, sb_users B, sb_conversations C WHERE B.id = A.user_id AND A.conversation_id = C.id AND A.id IN (SELECT MAX(A.id) FROM sb_messages A, sb_conversations B WHERE A.' . (is_numeric($datetime) ? ('id > ' . $datetime) : ('creation_time > "' . $datetime . '"')) . ' AND A.conversation_id = B.id AND B.user_id = ' . $user_id . ' GROUP BY A.conversation_id) GROUP BY conversation_id ORDER BY C.id DESC', false);
}

function sb_search_conversations($search) {
    $search = trim(sb_db_escape(mb_strtolower($search)));
    $search_first = explode(' ', $search);
    $numeric_filters = '';
    $tags_filter = '';
    if (count($search_first) < 4 && strlen($search_first[0]) > 2) {
        $search_first = $search_first[0];
    } else {
        $search_first = $search;
    }
    if (is_numeric($search)) {
        $search_int = intval($search);
        $numeric_filters = " OR C.id = $search_int OR C.department = $search_int OR C.agent_id = $search_int";
    }
    if (!sb_get_setting('disable-tags')) {
        $tags_filter = ' OR LOWER(C.tags) LIKE "%' . $search . '%"';
    }
    $query = SELECT_CONVERSATIONS . 'FROM sb_messages A JOIN sb_users B ON B.id = A.user_id JOIN sb_conversations C ON C.id = A.conversation_id' . sb_routing_and_department_db('C') . ' WHERE (LOWER(A.message) LIKE "%' . $search . '%" OR LOWER(A.attachments) LIKE "%' . $search . '%" OR LOWER(B.first_name) LIKE "%' . $search_first . '%" OR LOWER(B.last_name) LIKE "%' . $search_first . '%" OR LOWER(B.email) LIKE "%' . $search . '%" OR LOWER(C.title) LIKE "%' . $search . '%"' . $numeric_filters . $tags_filter . ') GROUP BY A.conversation_id ORDER BY A.creation_time DESC';
    $result = sb_db_get($query, false);
    if (isset($result) && is_array($result)) {
        return sb_get_conversations_users($result);
    } else {
        return sb_error('db-error', 'sb_search_conversations', $result);
    }
}

function sb_search_user_conversations($search, $user_id = false) {
    $search = trim(sb_db_escape(mb_strtolower($search)));
    return sb_db_get(SELECT_CONVERSATIONS . 'FROM sb_messages A, sb_users B, sb_conversations C WHERE A.conversation_id = C.id AND B.id = C.user_id AND B.id = ' . ($user_id === false ? sb_get_active_user_ID() : sb_db_escape($user_id, true)) . ' AND (LOWER(A.message) LIKE "%' . $search . '%" OR LOWER(A.attachments) LIKE "%' . $search . '%" OR LOWER(C.title) LIKE "%' . $search . '%") GROUP BY A.conversation_id ORDER BY A.creation_time DESC', false);
}

function sb_get_user_conversations($user_id, $exclude_id = -1, $agent = false) {
    $exclude = $exclude_id != -1 ? ' AND A.conversation_id <> ' . sb_db_escape($exclude_id) : '';
    $user_id = sb_db_escape($user_id, true);
    $ids = sb_db_get($agent ? 'SELECT conversation_id AS `id` FROM sb_messages WHERE user_id = ' . $user_id . ' GROUP BY conversation_id' : 'SELECT id FROM sb_conversations WHERE user_id = ' . $user_id . ' GROUP BY id', false);
    $ids_string = '';
    $count = count($ids);
    if ($count) {
        for ($i = 0; $i < $count; $i++) {
            $ids_string .= $ids[$i]['id'] . ',';
        }
        return sb_db_get(SELECT_CONVERSATIONS . 'FROM sb_messages A, sb_users B, sb_conversations C WHERE B.id = A.user_id' . sb_routing_and_department_db('C') . ' AND A.conversation_id = C.id AND A.id IN (SELECT max(A.id) FROM sb_messages A, sb_conversations C WHERE (A.message <> "" OR A.attachments <> "") AND A.conversation_id = C.id' . ($agent ? '' : ' AND C.user_id = ' . $user_id) . $exclude . ' GROUP BY conversation_id)' . ($ids_string ? ' AND A.conversation_id IN (' . substr($ids_string, 0, -1) . ')' : '') . ' GROUP BY conversation_id ORDER BY A.id DESC', false);
    }
    return [];
}

function sb_get_last_conversation_id_or_create($user_id, $status_code = 1) {
    $conversation_id = sb_isset(sb_db_get('SELECT id FROM sb_conversations WHERE user_id = ' . sb_db_escape($user_id, true) . ' ORDER BY id DESC LIMIT 1'), 'id');
    return $conversation_id ? $conversation_id : sb_isset(sb_isset(sb_new_conversation($user_id, $status_code), 'details'), 'id');
}

function sb_get_new_messages($user_id, $conversation_id, $last_datetime, $last_id = false) {
    $last_datetime = sb_db_escape($last_datetime);
    $last_id = $last_id ? sb_db_escape($last_id, true) : false;
    $result = sb_db_get('SELECT sb_messages.*, sb_users.first_name, sb_users.last_name, sb_users.profile_image, sb_users.user_type FROM sb_messages, sb_users, sb_conversations WHERE (sb_messages.creation_time > "' . $last_datetime . '"' . ($last_id ? (' OR sb_messages.id > ' . $last_id) : '') . ') AND sb_messages.conversation_id = ' . sb_db_escape($conversation_id, true) . ' AND sb_users.id = sb_messages.user_id AND sb_conversations.user_id = ' . sb_db_escape($user_id, true) . ' AND sb_messages.conversation_id = sb_conversations.id ORDER BY sb_messages.id ASC', false);
    return isset($result) && is_array($result) ? $result : sb_error('db-error', 'sb_get_new_messages', $result);
}

function sb_get_conversation($user_id = false, $conversation_id = false) {
    $user_id = $user_id ? sb_db_escape($user_id, true) : sb_get_active_user_ID();
    $conversation_id = sb_db_escape($conversation_id, true);
    $messages = sb_db_get('SELECT sb_messages.*, sb_users.first_name, sb_users.last_name, sb_users.profile_image, sb_users.user_type FROM sb_messages, sb_users, sb_conversations WHERE sb_messages.conversation_id = ' . $conversation_id . (sb_is_agent() ? '' : ' AND sb_conversations.user_id = ' . $user_id) . ' AND sb_messages.conversation_id = sb_conversations.id AND sb_users.id = sb_messages.user_id ORDER BY sb_messages.id ASC', false);
    if (isset($messages) && is_array($messages)) {
        $details = sb_db_get('SELECT sb_users.id as user_id, sb_users.first_name, sb_users.last_name, sb_users.profile_image, sb_users.user_type, sb_conversations.* FROM sb_users, sb_conversations WHERE sb_conversations.id = ' . $conversation_id . (sb_is_agent() ? '' : ' AND sb_users.id = ' . $user_id) . ' AND sb_users.id = sb_conversations.user_id LIMIT 1');
        if ($details) {
            if (sb_is_error($details)) {
                return $details;
            }
            $details['busy'] = false;
            if (sb_is_agent()) {
                $active_user = sb_get_active_user();
                if ($active_user) {
                    $is_queue = sb_get_multi_setting('queue', 'queue-active');
                    $is_routing = sb_get_multi_setting('routing', 'routing-active');
                    $is_hide_conversations = sb_get_multi_setting('agent-hide-conversations', 'agent-hide-conversations-active');
                    $is_show_unassigned_conversations = sb_get_multi_setting('agent-hide-conversations', 'agent-hide-conversations-view');
                    if (sb_is_agent($active_user, true, false, true) && ((!empty($active_user['department']) && $active_user['department'] != $details['department']) || ($is_hide_conversations && !$is_show_unassigned_conversations && empty($details['agent_id'])) || (!empty($details['agent_id']) && $active_user['id'] != $details['agent_id'] && ($is_queue || $is_routing || $is_hide_conversations)))) {
                        return 'agent-not-authorized';
                    }
                    if ($is_show_unassigned_conversations || (!$is_queue && !$is_routing && !$is_hide_conversations)) {
                        $agent_id = sb_is_active_conversation_busy($conversation_id, sb_get_active_user_ID());
                        if ($agent_id) {
                            $details['busy'] = sb_get_user($agent_id);
                        }
                        sb_set_agent_active_conversation($conversation_id);
                    }
                    if (!sb_get_setting('disable-notes')) {
                        $details['notes'] = sb_get_notes($conversation_id);
                    }
                    $details['tags'] = $details['tags'] ? explode(',', $details['tags']) : [];
                }
                $rating = sb_isset(sb_get_external_setting('ratings'), $conversation_id);
                if ($rating) {
                    $details['rating'] = $rating;
                }
            } else {
                if ($details['status_code'] == 1 && $details['status_code'] != 5) {
                    sb_update_conversation_status($conversation_id, 0);
                    $details['status_code'] == 0;
                }
                if (sb_chatbot_active() && sb_get_multi_setting('dialogflow-human-takeover', 'dialogflow-human-takeover-active')) {
                    $time = sb_gmt_now(864000, true);
                    for ($i = count($messages) - 1; $i > -1; $i--) {
                        $message = $messages[$i];
                        $payload = sb_isset($message, 'payload');
                        if (strpos($payload, '"conversation-status-update-3"')) {
                            break;
                        } else if ((sb_is_agent($message['user_type'], true) && strtotime($message['creation_time']) > $time) || strpos($payload, '"human-takeover":true')) {
                            $details['is_human_takeover'] = true;
                            break;
                        }
                    }
                }
            }
            return ['messages' => $messages, 'details' => $details];
        }
    } else {
        return sb_error('db-error', 'sb_get_conversation', $messages);
    }
    return false;
}

function sb_new_conversation($user_id, $status_code = 1, $title = '', $department = -1, $agent_id = -1, $source = false, $extra = false, $extra_2 = false, $extra_3 = false, $tags = false) {
    if (!sb_isset_num($agent_id)) {
        if (sb_get_multi_setting('routing', 'routing-active') && !sb_get_multi_setting('queue', 'queue-active')) {
            $agent_id = sb_routing(-1, $department);
        }
    } else if (defined('SB_AECOMMERCE')) {
        $agent_id = sb_aecommerce_get_agent_id($agent_id);
    }
    if (strlen($tags) > 255) {
        $tags = substr($tags, 0, 255);
    }
    $user_id = sb_db_escape($user_id, true);
    $conversation_id = sb_db_query('INSERT INTO sb_conversations(user_id, title, status_code, creation_time, department, agent_id, source, extra, extra_2, extra_3, tags) VALUES (' . $user_id . ', "' . sb_db_escape(ucfirst($title)) . '", "' . ($status_code == -1 || $status_code === false || $status_code === '' ? 2 : sb_db_escape($status_code)) . '", "' . sb_gmt_now() . '", ' . (sb_isset_num($department) ? sb_db_escape($department) : 'NULL') . ', ' . (sb_isset_num($agent_id) ? sb_db_escape($agent_id, true) : 'NULL') . ', ' . ($source || $source !== 'chat' ? '"' . sb_db_escape($source) . '"' : 'NULL') . ', ' . ($extra ? '"' . sb_db_escape($extra) . '"' : 'NULL') . ', ' . ($extra_2 ? '"' . sb_db_escape($extra_2) . '"' : 'NULL') . ', ' . ($extra_3 ? '"' . sb_db_escape($extra_3) . '"' : 'NULL') . ', ' . ($tags ? '"' . sb_db_escape(is_string($tags) ? $tags : implode(',', $tags)) . '"' : 'NULL') . ')', true);
    if (is_numeric($conversation_id)) {
        $conversation = sb_get_conversation($user_id, $conversation_id);
        if (sb_pusher_active()) {
            sb_pusher_trigger('private-user-' . $user_id, 'new-conversation', ['conversation_user_id' => $user_id, 'conversation_id' => $conversation_id]);
        }
        sb_webhooks('SBNewConversationCreated', $conversation);
        return $conversation;
    } else if (sb_is_error($conversation_id) && sb_db_get('SELECT count(*) as count FROM sb_users WHERE id = ' . $user_id)['count'] == 0) {
        return new SBValidationError('user-not-found');
    }
    return $conversation_id;
}

function sb_update_conversation_status($conversation_id, $status) {
    $response = false;
    $conversation_id = sb_db_escape($conversation_id, true);
    $agent = sb_is_agent();
    if (empty($conversation_id)) {
        return false;
    }
    if (in_array($status, [0, 1, 2, 3, 4])) {
        $response = sb_db_query('UPDATE sb_conversations SET status_code = ' . sb_db_escape($status) . ' WHERE id = ' . $conversation_id);
        if ($status == 3 || $status == 4) {
            sb_db_query('DELETE FROM sb_messages WHERE payload = "{\"human-takeover\":true}" AND conversation_id = ' . $conversation_id);
            $GLOBALS['human-takeover-' . $conversation_id] = false;
        }
    } else {
        if ($status == 5 && $agent) {
            $ids = sb_db_get('SELECT id FROM sb_conversations WHERE status_code = 4', false);
            $count = count($ids);
            if ($count) {
                for ($i = 0; $i < $count; $i++) {
                    sb_delete_attachments($ids[$i]['id']);
                }
                sb_db_query('DELETE FROM sb_settings WHERE name IN (' . implode(', ', array_map(function ($e) {
                    return '"notes-' . $e . '"';
                }, array_column($ids, 'id'))) . ')');
                $response = sb_db_query('DELETE FROM sb_conversations WHERE status_code = 4');
            }
        } else {
            $response = new SBValidationError('invalid-status-code');
        }
    }
    if ($agent && in_array($status, [3, 4]) && sb_get_setting('logs')) {
        sb_logs('changed the status of the conversation #' . $conversation_id . ' to ' . ($status == 3 ? 'archived' : 'deleted'));
    }
    if (in_array($status, [3, 4]) && $agent) {
        sb_update_conversation_event('conversation-status-update-' . $status, $conversation_id);
    }
    if ($status == 3 && $agent && sb_pusher_active() && (sb_get_setting('close-chat') || sb_get_multi_setting('rating-message', 'rating-active') || sb_get_setting('close-ticket') || sb_chatbot_active())) {
        sb_pusher_trigger('private-user-' . sb_db_get('SELECT user_id FROM sb_conversations WHERE id = ' . $conversation_id)['user_id'], 'new-message');
    }
    if (($agent && $status != 2) || (!$agent && $status != 0 && $status != 1)) {
        sb_remove_email_cron($conversation_id);
    }
    sb_webhooks('SBActiveConversationStatusUpdated', ['conversation_id' => $conversation_id, 'status_code' => $status]);
    return $response;
}

function sb_update_conversation_department($conversation_id, $department, $message = false) {
    if (sb_conversation_security_error($conversation_id) || (!sb_get_multi_setting('agents', 'agents-update-department') && sb_is_agent(false, true, false, true))) {
        return sb_error('security-error', 'sb_update_conversation_department');
    }
    $empty_department = empty($department) || $department == -1;
    $response = sb_db_query('UPDATE sb_conversations SET department = ' . ($empty_department ? 'NULL' : sb_db_escape($department)) . ' WHERE id = ' . sb_db_escape($conversation_id, true));
    if ($response) {
        if ($message) {
            sb_send_agents_notifications($message, str_replace('{T}', sb_is_agent() ? sb_get_user_name() : sb_get_setting('bot-name', 'Chatbot'), sb_('This message has been sent because {T} assigned this conversation to your department.')), $conversation_id, false, false, ['force' => true]);
        }
        sb_update_conversation_event('conversation-department-update-' . $department, $conversation_id, $message);
        if (sb_get_setting('logs')) {
            sb_logs('assigned the conversation #' . $conversation_id . ' to the department ' . ($empty_department ? 'None' : '#' . $department));
        }
        return true;
    }
    return sb_error('department-update-error', 'sb_update_conversation_department', $response);
}

function sb_update_conversation_agent($conversation_id, $agent_id, $message = false) {
    if (sb_conversation_security_error($conversation_id)) {
        return sb_error('security-error', 'sb_update_conversation_agent');
    }
    $conversation_id = sb_db_escape($conversation_id, true);
    if ($agent_id == 'routing' || $agent_id == 'routing-unassigned') {
        $agent_id = sb_routing(false, sb_isset(sb_db_get('SELECT department FROM sb_conversations WHERE id = ' . $conversation_id), 'department'), $agent_id == 'routing-unassigned');
    }
    $empty_agent_id = empty($agent_id);
    if (!$empty_agent_id && !in_array(sb_isset(sb_db_get('SELECT user_type FROM sb_users WHERE id = ' . sb_db_escape($agent_id, true)), 'user_type'), ['agent', 'admin'])) {
        return sb_error('not-an-agent', 'sb_update_conversation_agent');
    }
    $response = sb_db_query('UPDATE sb_conversations SET agent_id = ' . ($empty_agent_id ? 'NULL' : sb_db_escape($agent_id, true)) . ', status_code = 2 WHERE id = ' . $conversation_id);
    if ($response) {
        if ($message) {
            sb_send_agents_notifications($message, $empty_agent_id ? '' : str_replace('{T}', sb_is_agent() ? sb_get_user_name() : sb_get_setting('bot-name', 'Chatbot'), sb_('This message has been sent because {T} assigned this conversation to you.')), $conversation_id, false, false, ['force' => true]);
        }
        if (!$empty_agent_id) {
            sb_update_conversation_event('conversation-agent-update-' . $agent_id, $conversation_id, $message);
        }
        if (sb_get_setting('logs')) {
            sb_logs('assigned the conversation #' . $conversation_id . ' to the agent ' . ($empty_agent_id ? 'None' : '#' . $agent_id));
        }
        return true;
    }
    return sb_error('agent-update-error', 'sb_update_conversation_agent', $response);
}

function sb_update_conversation_event($payload_event, $conversation_id, $message_preview = false) {
    $payload = ['event' => $payload_event];
    if ($message_preview) {
        $payload['preview'] = $message_preview;
    }
    sb_db_query('INSERT INTO sb_messages(user_id, message, creation_time, status_code, attachments, payload, conversation_id) VALUES (' . sb_get_active_user_ID() . ', "", "' . sb_gmt_now() . '", 0, "", "' . sb_db_json_escape($payload) . '", ' . sb_db_escape($conversation_id, true) . ')');
    if (sb_pusher_active()) {
        sb_pusher_trigger('agents', 'update-conversations', ['conversation_id' => $conversation_id]);
    }
}

function sb_transcript($conversation_id, $type = false) {
    if (sb_conversation_security_error($conversation_id)) {
        return sb_error('security-error', 'sb_transcript');
    }
    $messages = sb_db_get('SELECT id, user_id, message, creation_time, attachments, payload FROM sb_messages WHERE conversation_id = ' . sb_db_escape($conversation_id, true), false);
    $count = count($messages);
    $file_name = 'conversation-' . $conversation_id . '-' . rand(100000, 999999999);
    $utc_offset = sb_get_setting('timetable-utc', 0);
    $users = [];
    if ($count) {
        if ($type === false) {
            $type = sb_get_setting('transcript-type', 'txt');
        }
        if ($type == 'csv') {
            for ($i = 0; $i < $count; $i++) {
                $messages[$i]['creation_time'] = sb_gmt_date_to_local($messages[$i]['creation_time'], $utc_offset);
            }
            return sb_csv($messages, ['ID', 'User ID', 'Message', 'Creation date', 'Attachments', 'Payload'], $file_name);
        }
        if ($type == 'txt') {
            $code = '';
            $code_translation = '';
            $translation_language_code = sb_isset(json_decode($messages[0]['payload'], true), 'translation-language');
            for ($i = 0; $i < $count; $i++) {
                $message = $messages[$i];
                if ($message['message']) {
                    $user_id = $message['user_id'];
                    if (!isset($users[$user_id])) {
                        $users[$user_id] = sb_get_user_name(sb_get_user($user_id)) . ' | ID ' . $user_id . ' | ';
                    }
                    $date = sb_gmt_date_to_local($message['creation_time'], $utc_offset);
                    $code .= $users[$user_id] . $date . PHP_EOL . $message['message'] . PHP_EOL . PHP_EOL;
                    if ($translation_language_code) {
                        $code_translation .= $users[$user_id] . $date . PHP_EOL . sb_google_get_message_translation($message, $translation_language_code)['message'] . PHP_EOL . PHP_EOL;
                    }
                }
            }
            if ($translation_language_code) {
                $code = sb_google_get_language_name(sb_get_user_language($messages[0]['user_id'])) . PHP_EOL . PHP_EOL . $code . PHP_EOL . PHP_EOL . sb_google_get_language_name($translation_language_code) . PHP_EOL . PHP_EOL . $code_translation;
            }
            sb_file(sb_upload_path() . '/' . $file_name . '.txt', $code);
            return sb_upload_path(true) . '/' . $file_name . '.txt';
        }
    }
    return false;
}

function sb_get_notes($conversation_id) {
    return sb_get_external_setting('notes-' . $conversation_id, []);
}

function sb_add_note($conversation_id, $user_id, $name, $message) {
    $notes = sb_get_notes($conversation_id);
    $id = rand(0, 99999);
    array_push($notes, ['id' => $id, 'user_id' => $user_id, 'name' => $name, 'message' => sb_sanatize_string($message), 'date' => sb_gmt_now()]);
    $response = sb_save_external_setting('notes-' . $conversation_id, $notes);
    return $response ? $id : $response;
}

function sb_update_note($conversation_id, $user_id, $note_id, $message) {
    $notes = sb_get_notes($conversation_id);
    for ($i = 0; $i < count($notes); $i++) {
        if ($notes[$i]['id'] == $note_id) {
            $notes[$i]['message'] = sb_sanatize_string($message);
            $notes[$i]['user_id'] = $user_id;
            $notes[$i]['date'] = sb_gmt_now();
            return sb_save_external_setting('notes-' . $conversation_id, $notes);
        }
    }
    return false;
}

function sb_delete_note($conversation_id, $note_id) {
    $notes = sb_get_notes($conversation_id);
    for ($i = 0; $i < count($notes); $i++) {
        if ($notes[$i]['id'] == $note_id) {
            array_splice($notes, $i, 1);
            return count($notes) ? sb_save_external_setting('notes-' . $conversation_id, $notes) : sb_db_query('DELETE FROM sb_settings WHERE name = "notes-' . sb_db_escape($conversation_id) . '" LIMIT 1');
        }
    }
    return false;
}

function sb_direct_message($user_ids, $message) {
    $sources = ['whatsapp' => 'wa', 'messenger' => 'fb', 'telegram' => 'tg', 'viber' => 'vb', 'twitter' => 'tw', 'instagram' => 'ig', 'line' => 'ln', 'wechat' => 'wc', 'zalo' => 'za', 'google' => 'bm', 'tickets' => 'tk'];
    if (is_string($user_ids) && ($user_ids == 'all' || isset($sources[$user_ids]))) {
        $items = sb_db_get($user_ids == 'all' ? 'SELECT id FROM sb_users WHERE user_type <> "agent" AND user_type <> "admin" AND user_type <> "bot"' : 'SELECT A.id FROM sb_users A, sb_conversations B WHERE B.source = "' . sb_db_escape($sources[$user_ids]) . '" AND B.user_id = A.id GROUP BY A.id', false);
        $user_ids = [];
        for ($i = 0; $i < count($items); $i++) {
            array_push($user_ids, $items[$i]['id']);
        }
    }
    $user_ids = is_string($user_ids) ? explode(',', str_replace(' ', '', $user_ids)) : $user_ids;
    $user_ids_string = substr(json_encode($user_ids), 1, -1);
    $missing = sb_db_get('SELECT id FROM sb_users WHERE id NOT IN (' . $user_ids_string . ') AND id NOT IN (SELECT user_id FROM sb_conversations)', false);
    if (!empty($missing)) {
        $query = 'INSERT INTO sb_conversations(user_id, title, status_code, creation_time) VALUES ';
        for ($i = 0; $i < count($missing); $i++) {
            $query .= '(' . $missing[$i]['id'] . ', "", 1, NOW()),';
        }
        sb_db_query(substr($query, 0, -1));
    }
    $conversations = sb_db_get('SELECT user_id, id FROM sb_conversations WHERE user_id IN (' . $user_ids_string . ') GROUP BY user_id', false);
    $conversation_user_ids = array_column($conversations, 'user_id');
    foreach ($user_ids as $user_id) {
        if (!in_array($user_id, $conversation_user_ids)) {
            $conversation = sb_new_conversation($user_id);
            array_push($conversations, ['user_id' => $user_id, 'id' => $conversation['details']['id']]);
        }
    }
    $query = 'INSERT INTO sb_messages(user_id, message, creation_time, status_code, attachments, payload, conversation_id) VALUES ';
    $active_user = sb_get_active_user();
    $active_user_id = $active_user['id'];
    $now = sb_gmt_now();
    $count = count($conversations);
    for ($i = 0; $i < $count; $i++) {
        $query .= '(' . $active_user_id . ', "' . sb_db_escape(sb_merge_fields($message, [sb_get_user($conversations[$i]['user_id'])])) . '", "' . $now . '", 0, "", "", ' . $conversations[$i]['id'] . '),';
    }
    $response = sb_db_query(substr($query, 0, -1));
    if (sb_is_error($response)) {
        return new SBValidationError($response);
    }

    // Pusher
    if (sb_pusher_active()) {
        $channels = [];
        for ($i = 0; $i < count($user_ids); $i++) {
            array_push($channels, 'private-user-' . $user_ids[$i]);
        }
        sb_pusher_trigger($channels, 'new-message');
        sb_update_users_last_activity($active_user_id);
    }

    // Push notifications
    if (sb_get_multi_setting('push-notifications', 'push-notifications-users-active')) {
        sb_push_notification(sb_get_user_name(), $message, $active_user['profile_image'], $user_ids);
    }

    // Messaging apps
    $conversations = sb_db_get('SELECT user_id, id, source, extra FROM sb_conversations WHERE source <> "" AND user_id IN (' . $user_ids_string . ')', false);
    for ($i = 0; $i < count($conversations); $i++) {
        sb_messaging_platforms_send_message($message, $conversations[$i]);
    }

    sb_reports_update('direct-messages', mb_substr($message, 0, 18) . ' | ' . $count);
    return $response;
}

function sb_get_agents_in_conversation($conversation_id) {
    $rows = sb_db_get('SELECT A.id, first_name, last_name, profile_image, B.conversation_id FROM sb_users A, sb_messages B WHERE (A.user_type = "agent" OR A.user_type = "admin") AND A.id = B.user_id AND conversation_id ' . (is_array($conversation_id) ? ('IN (' . sb_db_escape(implode(',', $conversation_id)) . ')') : ('= ' . sb_db_escape($conversation_id, true))) . (sb_is_agent() ? '' : ' AND conversation_id in (SELECT id FROM sb_conversations WHERE user_id = ' . sb_get_active_user_ID() . ')') . ' GROUP BY A.id, B.conversation_id', false);
    $response = [];
    for ($i = 0; $i < count($rows); $i++) {
        if (isset($response[$rows[$i]['conversation_id']]))
            array_push($response[$rows[$i]['conversation_id']], $rows[$i]);
        else
            $response[$rows[$i]['conversation_id']] = [$rows[$i]];
    }
    return $response;
}

function sb_conversation_security_error($conversation_id) {
    return !sb_is_agent() && empty($GLOBALS['SB_FORCE_ADMIN']) && sb_isset(sb_db_get('SELECT user_id FROM sb_conversations WHERE id = ' . $conversation_id), 'user_id') != sb_get_active_user_ID();
}

function sb_set_agent_active_conversation($conversation_id, $agent_id = false) {
    $agent_id = $agent_id ? $agent_id : sb_get_active_user_ID();
    $active_agents_conversations = sb_get_external_setting('active_agents_conversations', []);
    $previous_conversation_id = sb_isset($active_agents_conversations, $agent_id, [false]);
    $active_agents_conversations[$agent_id] = [$conversation_id, time()];
    sb_save_external_setting('active_agents_conversations', $active_agents_conversations);
    if (sb_pusher_active())
        sb_pusher_trigger('agents', 'agent-active-conversation-changed', ['agent_id' => $agent_id, 'previous_conversation_id' => $previous_conversation_id[0], 'conversation_id' => $conversation_id]);
}

function sb_is_active_conversation_busy($conversation_id, $skip = -1) {
    $items = sb_get_external_setting('active_agents_conversations', []);
    $time = time();
    if (empty($items)) {
        return false;
    }
    foreach ($items as $agent_id => $value) {
        if ($agent_id != $skip && $value[0] == $conversation_id && ($time - 3600) < $value[1] && sb_is_user_online($agent_id)) {
            return $agent_id;
        }
    }
    return false;
}

function sb_count_conversations($status_code = false) {
    return sb_isset(sb_db_get('SELECT COUNT(*) AS count FROM sb_conversations' . ($status_code ? ' WHERE status_code = ' . sb_db_escape($status_code) . sb_routing_and_department_db() : '')), 'count');
}

function sb_send_agents_notifications($message, $bottom_message = false, $conversation_id = false, $attachments = false, $user = false, $extra = false) {
    $user = $user ? $user : (sb_is_agent() ? sb_get_user_from_conversation($conversation_id) : sb_get_active_user());
    $user_name = sb_get_user_name($user);
    $recipients = 'agents';
    $is_online = false;
    $force = sb_isset($extra, 'force');
    if ($conversation_id) {
        $conversation = sb_db_get('SELECT agent_id, department FROM sb_conversations WHERE id = ' . sb_db_escape($conversation_id, true));
        if ($conversation['department']) {
            $recipients = 'department-' . $conversation['department'];
        } else if ($conversation['agent_id']) {
            $recipients = $conversation['agent_id'];
            $is_online = sb_get_setting('stop-notify-admins') && sb_is_agent(sb_get_user($recipients), true, true) ? true : sb_is_user_online($recipients);
        }
    }
    if (!$is_online) {
        if ($force || sb_get_setting('notify-agent-email')) {
            sb_email_create($recipients, $user_name, $user['profile_image'], (empty($extra['email']) ? $message : $extra['email']) . ($bottom_message ? ('<br><br><span style="color:#a8a8a8;font-size:12px;">' . $bottom_message . '</span>') : ''), $attachments, false, $conversation_id);
        }
        if ($force || sb_get_multi_setting('sms', 'sms-active-agents')) {
            sb_send_sms($message, $recipients, true, $conversation_id, $attachments);
        }
    }
    if ($force || sb_get_multi_setting('push-notifications', 'push-notifications-active')) {
        sb_push_notification($user_name, $message, $user['profile_image'], $recipients, $conversation_id, sb_isset($user, 'id'), $attachments);
    }
    if (sb_pusher_active()) {
        sb_pusher_trigger('agents', 'update-conversations', ['conversation_id' => $conversation_id]);
    }
    return true;
}

function sb_check_conversations_assignment($conversation_ids, $agent_id = false, $department = false) {
    if (empty($conversation_ids)) {
        return [];
    }
    $conversation_ids = sb_db_get('SELECT id FROM sb_conversations WHERE id IN (' . sb_db_escape(implode(',', $conversation_ids)) . ') AND ' . ($agent_id ? ('agent_id <> ' . sb_db_escape($agent_id, true)) : '') . ($agent_id && $department ? ' AND ' : '') . ($department ? ('department <> ' . sb_db_escape($department)) : ''), false);
    for ($i = 0; $i < count($conversation_ids); $i++) {
        $conversation_ids[$i] = $conversation_ids[$i]['id'];
    }
    return $conversation_ids;
}

function sb_get_last_agent_in_conversation($conversation_id) {
    $agent = sb_db_get('SELECT B.id, B.first_name, B.last_name, B.email, B.user_type, B.token, B.department  FROM sb_messages A, sb_users B WHERE A.conversation_id = ' . sb_db_escape($conversation_id, true) . ' AND A.user_id = B.id AND (B.user_type = "agent" OR B.user_type = "admin") ORDER BY A.id LIMIT 1');
    return isset($agent['id']) ? $agent : false;
}

function sb_get_last_message($conversation_id, $exclude_message = false, $user_id = false) {
    return sb_db_get('SELECT message, attachments, payload FROM sb_messages WHERE (message <> "" || attachments <> "")' . ($exclude_message ? (' AND message <> "' . sb_db_escape($exclude_message) . '"') : '') . ' AND conversation_id = ' . sb_db_escape($conversation_id, true) . ($user_id ? (' AND user_id = ' . sb_db_escape($user_id, true)) : '') . ' ORDER BY id DESC LIMIT 1');
}

function sb_delete_attachments($conversation_id = false, $message_id = false) {
    $attachments_all = sb_db_get('SELECT attachments FROM sb_messages WHERE user_id <> ' . sb_get_bot_id() . '  AND ' . ($conversation_id ? 'conversation_id' : 'id') . ' = ' . sb_db_escape($conversation_id ? $conversation_id : $message_id, true), false);
    for ($i = 0; $i < count($attachments_all); $i++) {
        $attachments = sb_isset($attachments_all[$i], 'attachments');
        if ($attachments) {
            $attachments = json_decode($attachments, true);
            for ($j = 0; $j < count($attachments); $j++) {
                sb_file_delete($attachments[$j][1]);
            }
        }
    }
    return true;
}

function sb_update_messages_status($message_ids, $user_id = false) {
    $response = sb_db_query('UPDATE sb_messages SET status_code = 2 WHERE id IN (' . sb_db_escape(implode(',', $message_ids)) . ')');
    if ($user_id && sb_pusher_active()) {
        sb_pusher_trigger('private-user-' . $user_id, 'message-status-update', ['message_ids' => $message_ids]);
    }
    return $response;
}

function sb_update_conversation_extra($conversation_id, $extra = false, $extra_2 = false, $extra_3 = false) {
    if (!$extra && !$extra_2 && !$extra_3) {
        return false;
    } else {
        if ($extra == 'NULL') {
            $extra = '';
        }
        if ($extra_2 == 'NULL') {
            $extra_2 = '';
        }
        if ($extra_3 == 'NULL') {
            $extra_3 = '';
        }
    }
    return sb_db_query('UPDATE sb_conversations SET ' . str_replace('SET ,', 'SET ', ($extra !== false ? 'extra = "' . sb_db_escape($extra) . '"' : '') . ($extra_2 !== false ? ', extra_2 = "' . sb_db_escape($extra_2) . '"' : '') . ($extra_3 !== false ? ', extra_3 = "' . sb_db_escape($extra_3) . '"' : '') . ' WHERE id = ' . sb_db_escape($conversation_id, true)));
}

/*
 * -----------------------------------------------------------
 * MESSAGES
 * -----------------------------------------------------------
 *
 * 1. Add a message to a conversation
 * 2. Update an existing message
 * 3. Delete a message
 * 4. Send the close message
 * 5. Convert the merge fields to the final values
 * 6. Update the tags assigned to a conversation
 * 7. Save a voice message
 * 8. Archive or restore messages
 *
 */

function sb_send_message($sender_id, $conversation_id, $message = '', $attachments = [], $conversation_status_code = -1, $payload = false, $queue = false, $recipient_id = false) {
    $pusher = sb_pusher_active();
    $conversation_id = sb_db_escape($conversation_id, true);
    $user_id = $sender_id;
    if (!$sender_id || $sender_id == -1) {
        $sender_id = sb_get_active_user_ID();
    } else {
        $sender_id = sb_db_escape($sender_id, true);
    }
    if ($sender_id) {
        $attachments_json = '';
        $security = sb_is_agent();
        $attachments = sb_json_array($attachments);
        $conversation = sb_db_get('SELECT status_code, agent_id, user_id, department, source, extra FROM sb_conversations WHERE id = ' . $conversation_id);
        if (!$conversation || sb_is_error($conversation)) {
            return trigger_error('Conversation not found.');
        }
        $conversation_source = sb_isset($conversation, 'source');
        $sender = sb_get_user($sender_id);
        $user = sb_db_get('SELECT * FROM sb_users WHERE id = ' . $conversation['user_id']);
        $user_id = $user['id'];
        $is_sender_agent = sb_is_agent($sender);
        $is_sender_bot = sb_isset($sender, 'user_type') == 'bot';
        $is_chatbot_active = sb_chatbot_active(true, true, $conversation_source);
        $is_human_takeover_active = $is_chatbot_active && sb_dialogflow_is_human_takeover($conversation_id);
        $is_human_takeover = $is_chatbot_active && !$is_human_takeover_active && sb_get_multi_setting('dialogflow-human-takeover', 'dialogflow-human-takeover-active');
        $last_agent = false;
        $count_attachments = count($attachments);
        if (!$message && $count_attachments && strpos($attachments[0][0], 'voice_message') && defined('SB_DIALOGFLOW') && !sb_chatbot_active() && sb_get_multi_setting('open-ai', 'open-ai-speech-recognition')) {
            $message = sb_open_ai_audio_to_text($attachments[0][1], false, $sender_id);
        }
        if ($is_sender_agent && !$is_sender_bot) {
            if ($is_chatbot_active && !$is_human_takeover_active) {
                $GLOBALS['human-takeover-' . $conversation_id] = true;
                $is_human_takeover_active = true;
                $is_human_takeover = false;
            }
            if (sb_get_multi_setting('open-ai', 'open-ai-spelling-correction')) {
                $message = sb_open_ai_spelling_correction($message);
            }
        }
        if ($count_attachments) {
            $attachments_json = '[';
            for ($i = 0; $i < $count_attachments; $i++) {
                $attachments_json .= '[\"' . sb_db_escape($attachments[$i][0]) . '\", \"' . sb_db_escape($attachments[$i][1]) . '\"' . (isset($attachments[$i][2]) ? ', \"' . $attachments[$i][2] . '\"' : '') . '],';
            }
            $attachments_json = substr($attachments_json, 0, -1) . ']';
        }
        if ($security || $user_id == sb_get_active_user_ID() || !empty($GLOBALS['SB_FORCE_ADMIN'])) {

            // Message sending
            if ($recipient_id) {
                global $SB_LANGUAGE;
                $SB_LANGUAGE = [sb_get_user_language($recipient_id), 'front'];
            }
            if (!$pusher) {
                sb_set_typing($sender_id, -1);
            }
            if (empty($payload)) {
                $payload = false;
            } else {
                $payload = sb_json_array($payload);
            }
            $message = sb_merge_fields($message, [sb_get_user_name($user), sb_isset($user, 'email')]);
            if (sb_is_cloud() && in_array(sb_defined('SB_CLOUD_MEMBERSHIP_TYPE', 'messages'), ['messages', 'messages-agents'])) {
                sb_cloud_increase_count();
            }
            $message = preg_replace("/(\n){3,}/", "\n\n", str_replace(["\r", "\t"], "", $message));
            $response = sb_db_query('INSERT INTO sb_messages(user_id, message, creation_time, status_code, attachments, payload, conversation_id) VALUES ("' . $sender_id . '", "' . sb_db_escape($message) . '", "' . sb_gmt_now() . '", 0, "' . $attachments_json . '", "' . ($payload ? sb_db_json_escape($payload) : '') . '", "' . $conversation_id . '")', true);

            if (!sb_is_agent()) {

                // Queue
                if ($queue) {
                    if ($conversation['status_code'] == 3) {
                        sb_routing_assign_conversation(null, $conversation_id);
                        $conversation['agent_id'] = '';
                    } else {
                        $queue = false;
                    }
                } else if ($conversation['status_code'] == 3 && (sb_get_multi_setting('routing', 'routing-active') || sb_get_multi_setting('agent-hide-conversations', 'agent-hide-conversations-active'))) {

                    // Routing change agent if offline
                    $last_agent = sb_get_last_agent_in_conversation($conversation_id);
                    if ($last_agent && !sb_is_user_online($last_agent['id'])) {
                        sb_update_conversation_agent($conversation_id, sb_get_multi_setting('routing', 'routing-active') ? 'routing' : 'routing-unassigned');
                    }
                }
            }

            // Conversation status code
            if ($conversation_status_code != 'skip') {
                if ($conversation_status_code == -1 || $conversation_status_code === false || !in_array($conversation_status_code, [0, 1, 2, 3, 4])) {
                    $conversation_status_code = $is_sender_agent && !$is_sender_bot ? 1 : ($is_human_takeover ? 1 : 2);
                }
                if ($conversation_status_code != $conversation['status_code']) {
                    sb_db_query('UPDATE sb_conversations SET status_code = ' . sb_db_escape($conversation_status_code) . ' WHERE id = ' . $conversation_id);
                    if ($conversation['status_code'] == 5) {
                        sb_messages_archiviation($conversation_id, true);
                    }
                    sb_webhooks('SBActiveConversationStatusUpdated', ['conversation_id' => $conversation_id, 'status_code' => $conversation_status_code]);
                }
            }

            if (sb_is_error($response)) {
                return $response;
            }
            if ($pusher) {
                $payload = ['conversation_user_id' => $user_id, 'message_id' => $response, 'conversation_id' => $conversation_id];
                sb_pusher_trigger('private-user-' . $user_id, 'new-message', $payload);
                sb_pusher_trigger('agents', 'update-conversations', $payload);
                sb_update_users_last_activity($sender_id);
            }

            // Notifications
            $response_notifications = [];
            $recipient_id = false;
            $queue_active = empty($conversation['agent_id']) && !$is_sender_agent && sb_get_multi_setting('queue', 'queue-active');
            $user_name = sb_get_user_name($sender);
            if ($is_sender_agent) {
                $recipient_id = $user_id;
            } else {
                $last_agent = $last_agent ? $last_agent : sb_get_last_agent_in_conversation($conversation_id);
                if ($last_agent) {
                    $recipient_id = $last_agent['id'];
                } else if (!empty($conversation['agent_id'])) {
                    $recipient_id = $conversation['agent_id'];
                } else if (!empty($conversation['department'])) {
                    $recipient_id = 'department-' . $conversation['department'];
                } else {
                    $recipient_id = 'agents';
                }
                if (!empty($user['email']) && defined('SB_TICKETS')) {
                    $channel = sb_get_setting('tickets-email-notification');
                    if (($channel && ($channel == 'all' || (!$conversation_source && $channel == 'c') || $channel == $conversation_source || ($channel == 'em-tk' && in_array($conversation_source, ['tk', 'em'])))) && sb_db_get('SELECT COUNT(*) AS `count` FROM sb_messages WHERE conversation_id = "' . $conversation_id . '" LIMIT 1')['count'] == 1) {
                        sb_tickets_email($user, $message, $attachments, $conversation_id);
                    }
                }
            }
            if (!$queue_active && !$is_human_takeover && ((!$is_sender_agent && sb_get_multi_setting('push-notifications', 'push-notifications-active')) || ($is_sender_agent && sb_get_multi_setting('push-notifications', 'push-notifications-users-active')))) {
                sb_push_notification($user_name, $message, $sender['profile_image'], $recipient_id, $conversation_id, $user_id, $attachments);
            }
            if ((!$queue_active || (!$is_sender_agent && !sb_agents_online())) && !$is_sender_bot && !$is_human_takeover) {
                $user_check = $is_sender_agent && (!sb_is_user_online($user_id) || $conversation_source == 'em');
                $agent_check = !$is_sender_agent && (!is_numeric($recipient_id) || !sb_is_user_online($recipient_id));
                if (($agent_check && sb_get_multi_setting('sms', 'sms-active-agents')) || ($user_check && sb_get_multi_setting('sms', 'sms-active-users') && !in_array($conversation['source'], ['wa', 'tg']))) {
                    $response_notification = sb_send_sms($message, $recipient_id, true, $conversation_id, $attachments);
                    if ($response_notification) {
                        array_push($response_notifications, 'sms');
                    }
                }
                if (($agent_check && sb_get_setting('notify-agent-email')) || (!empty($user['email']) && ($user_check && (sb_get_setting('notify-user-email') || $conversation['source'] == 'em' || sb_email_piping_is_active())))) {
                    if (sb_get_setting('notify-email-cron')) {
                        $cron_job_emails = sb_get_external_setting('cron-email-notifications', []);
                        if (!isset($cron_job_emails[$conversation_id])) {
                            $cron_job_emails[$conversation_id] = [$recipient_id, $user_name, $sender['profile_image'], $attachments, sb_isset($conversation, 'department'), !$is_sender_agent];
                            sb_save_external_setting('cron-email-notifications', $cron_job_emails);
                        }
                        array_push($response_notifications, 'email-cron');
                    } else {
                        $continue = !$agent_check;
                        if (!$continue) {
                            $previous_human_message_user_type = sb_db_get('SELECT A.user_type, B.creation_time FROM sb_users A, sb_messages B WHERE B.conversation_id = ' . $conversation_id . ' AND B.id <> ' . $response . ' AND B.user_id = A.id AND (A.user_type = "agent" OR A.user_type = "admin" OR A.id = ' . $user_id . ') ORDER BY B.id DESC LIMIT 1');
                            $continue = !$previous_human_message_user_type || sb_is_agent($previous_human_message_user_type['user_type']) || strtotime($previous_human_message_user_type['creation_time']) < sb_gmt_now(120, true);
                        }
                        if ($continue) {
                            $response_notification = sb_email_create($recipient_id, $user_name, $sender['profile_image'], $message, $attachments, sb_isset($conversation, 'department'), $conversation_id, $conversation_source == 'em' ? $conversation['extra'] : false);
                            if ($response_notification) {
                                array_push($response_notifications, 'email');
                            }
                        }
                    }
                }
            }
            if ($is_sender_agent && sb_get_setting('logs')) {
                sb_logs('sent the message #' . $response . ' in the conversation #' . $conversation_id, $sender);
            }
            sb_webhooks('SBMessageSent', ['user_id' => $sender_id, 'message_id' => $response, 'message' => $message, 'attachments' => $attachments, 'conversation_user_id' => $user_id, 'conversation_id' => $conversation_id, 'conversation_status_code' => $conversation_status_code, 'conversation_source' => $conversation['source']]);
            return ['id' => $response, 'queue' => $queue, 'human_takeover_active' => $is_human_takeover_active, 'notifications' => $response_notifications, 'message' => $message];
        }
        return sb_error('security-error', 'sb_send_message');
    } else {
        return sb_error('active-user-not-found', 'sb_send_message');
    }
}

function sb_update_message($message_id, $message = false, $attachments = false, $payload = false) {
    return sb_update_or_delete_message('update', $message_id, $message, $attachments, $payload);
}

function sb_delete_message($message_id) {
    return sb_update_or_delete_message('delete', $message_id);
}

function sb_update_or_delete_message($action, $message_id, $message = false, $attachments = false, $payload = false) {
    $pusher = sb_pusher_active();
    $security = sb_is_agent() || !empty($GLOBALS['SB_FORCE_ADMIN']);
    $conversation = false;
    $user_id = false;
    $response = false;
    $message_id = sb_db_escape($message_id, true);
    $conversation = sb_db_get('SELECT id, user_id, source, extra, extra_2, extra_3 FROM sb_conversations WHERE id = (SELECT conversation_id FROM sb_messages WHERE id = ' . $message_id . ')');
    if (!$security) {
        $user_id = sb_isset($conversation, 'user_id');
        if ($user_id == sb_get_active_user_ID()) {
            $security = true;
        }
    }
    if ($security) {
        if ($action == 'update') {
            if ($message === false && $payload === false && $attachments === false) {
                return new SBValidationError('missing-arguments');
            }
            if ($attachments !== false) {
                $attachments = sb_json_array($attachments, false);
            }
            if ($payload !== false) {
                $payload = sb_json_array($payload, false);
            }
            if (sb_isset($payload, 'event') == 'delete-message') {
                $attachments = '';
            }
            $response = sb_db_query('UPDATE sb_messages SET ' . ($message !== false ? 'message = "' . sb_db_escape($message) . '",' : '') . ' creation_time = "' . sb_gmt_now() . '"' . ($payload !== false ? ', payload = "' . sb_db_json_escape($payload) . '"' : '') . ($attachments !== false ? ', attachments = "' . sb_db_json_escape($attachments) . '"' : '') . ' WHERE id = ' . $message_id);
        }
        if ($action == 'delete') {
            if (in_array($conversation['source'], ['tg'])) {
                $payload = json_decode(sb_isset(sb_db_get('SELECT payload FROM sb_messages WHERE id = ' . $message_id), 'payload'), true);
            }
            $response = sb_db_query('DELETE FROM sb_messages WHERE id = ' . $message_id);
            if ($response === true) {
                sb_delete_attachments(false, $message_id);
                switch ($conversation['source']) {
                    case 'tg':
                        if ($payload && isset($payload['tgid']) && isset($conversation['extra']) && isset($conversation['extra_3'])) {
                            sb_telegram_delete_message($conversation['extra'], $payload['tgid'], $conversation['extra_3']);
                        }
                        break;
                }
            }
        }
        if (sb_is_agent() && sb_get_setting('logs')) {
            sb_logs($action . 'd the message #' . $message_id);
        }
        if ($response && $pusher) {
            $payload = ['conversation_user_id' => $user_id, 'message_id' => $message_id, 'conversation_id' => sb_isset($conversation, 'id')];
            sb_pusher_trigger('private-user-' . $user_id, 'new-message', $payload);
            sb_pusher_trigger('agents', 'update-conversations', $payload);
        }
        return $response;
    }
    return sb_error('security-error', 'sb_' . $action . '_message');
}

function sb_close_message($conversation_id, $bot_id = false) {
    $message = sb_get_multi_setting('close-message', 'close-msg');
    if ($message) {
        if (!$bot_id) {
            $bot_id = sb_get_bot_id();
        }
        $message_id = sb_send_message($bot_id, $conversation_id, $message, [], 3, ['type' => 'close-message'])['id'];
        return sb_messaging_platforms_send_message($message, $conversation_id, $message_id);
    }
    return false;
}

function sb_merge_fields($message, $marge_fields_values = []) {
    $replace = '';
    $marge_fields = ['user_name', 'user_email', 'agent_name', 'agent_email'];
    $marge_field = '';
    if (defined('SB_WOOCOMMERCE')) {
        $message = sb_woocommerce_merge_fields($message, $marge_fields_values);
    }
    for ($i = 0; $i < count($marge_fields); $i++) {
        if (strpos($message, '{' . $marge_fields[$i]) !== false) {
            $marge_field = '{' . $marge_fields[$i] . '}';
            $value = isset($marge_fields_values[$i]) ? $marge_fields_values[$i] : false;
            switch ($marge_fields[$i]) {
                case 'user_name':
                    $replace = $value ? $value : sb_get_user_name($value);
                    break;
                case 'user_email':
                    $replace = $value ? $value : sb_isset(sb_get_active_user(), 'email', '{user_email}');
                    break;
                case 'agent_name':
                    $replace = sb_is_agent() ? sb_get_user_name() : '';
                    break;
                case 'agent_email':
                    $replace = sb_is_agent() ? sb_isset(sb_get_active_user(), 'email', '') : '';
                    break;
            }
        }
        $message = str_replace($marge_field, $replace, $message);
    }
    if (sb_is_cloud()) {
        require_once(SB_CLOUD_PATH . '/account/functions.php');
        $message = shopify_merge_fields($message);
    }
    return $message;
}

function sb_tags_update($conversation_id, $tags, $add = false) {
    if (sb_conversation_security_error($conversation_id)) {
        return sb_error('security-error', 'sb_tags_update');
    }
    for ($i = 0; $i < count($tags); $i++) {
        $tags[$i] = trim(str_replace(',', '', $tags[$i]));
    }
    if ($add) {
        $existing_tags = sb_isset(sb_db_get('SELECT tags FROM sb_conversations WHERE id = ' . sb_db_escape($conversation_id, true)), 'tags');
        if ($existing_tags) {
            $existing_tags = explode(',', $existing_tags);
            for ($i = 0; $i < count($existing_tags); $i++) {
                if (!in_array($existing_tags[$i], $tags)) {
                    array_push($tags, $existing_tags[$i]);
                }
            }
        }
    }
    $tags = implode(',', $tags);
    if (strlen($tags) > 255) {
        $tags = substr($tags, 0, 255);
    }
    return sb_db_query('UPDATE sb_conversations SET tags = "' . sb_db_escape($tags) . '" WHERE id = ' . sb_db_escape($conversation_id, true));
}

function sb_audio_clip($audio) {
    $file_name = '/audio-' . rand(1000000, 999999999) . '.webm';
    $path = sb_upload_path(false, true) . $file_name;
    $url = false;
    sb_file($path, $audio);
    if (sb_get_multi_setting('amazon-s3', 'amazon-s3-active') || defined('SB_CLOUD_AWS_S3')) {
        $url_aws = sb_aws_s3($path);
        if (strpos($url_aws, 'http') === 0) {
            $url = $url_aws;
            unlink($path);
        } else {
            $url = sb_upload_path(true, true) . $file_name;
        }
    }
    return $url;
}

function sb_messages_archiviation($conversation_id, $is_restore = false) {
    $messages = sb_db_get('SELECT * FROM ' . ($is_restore ? 'sb_archive' : 'sb_messages') . ' WHERE conversation_id = ' . $conversation_id, false);
    $count = count($messages);
    if ($count) {
        $query = 'INSERT IGNORE INTO ' . ($is_restore ? 'sb_messages' : 'sb_archive') . '(id, user_id, message, creation_time, status_code, attachments, payload, conversation_id) VALUES ';
        for ($i = 0; $i < $count; $i++) {
            $query .= '(' . $messages[$i]['id'] . ', ' . $messages[$i]['user_id'] . ', "' . sb_db_escape($messages[$i]['message']) . '", "' . $messages[$i]['creation_time'] . '", ' . $messages[$i]['status_code'] . ', "' . sb_db_escape($messages[$i]['attachments']) . '", "' . sb_db_escape($messages[$i]['payload']) . '", ' . $conversation_id . '),';
        }
        $response = sb_db_query(substr($query, 0, -1));
        if ($response === true) {
            if (!$is_restore) {
                sb_db_query('UPDATE sb_conversations SET status_code = 5 WHERE id = ' . $conversation_id);
            }
            sb_db_query('DELETE FROM ' . ($is_restore ? 'sb_archive' : 'sb_messages') . ' WHERE conversation_id = ' . $conversation_id);
        }
    }
}

/*
 * -----------------------------------------------------------
 * RICH MESSAGES
 * -----------------------------------------------------------
 *
 * 1. Get the custom rich messages ids including the built in ones
 * 2. Get the rich message with the given name
 * 3. Escape a rich message shortcode value
 * 4. Return the full shortcode and its parameters
 * 5. Execute a bot message
 * 6. Check if a string includes a rich message
 *
 */

function sb_get_rich_messages_ids($include_custom = true) {
    $result = sb_get_external_setting('rich-messages');
    $ids = ['chips', 'buttons', 'select', 'inputs', 'card', 'slider-images', 'slider', 'list-image', 'list', 'button', 'video', 'image', 'rating', 'email', 'phone', 'registration', 'login', 'timetable', 'articles', 'table', 'share'];
    if ($include_custom && is_array($result) && isset($result['rich-messages']) && is_array($result['rich-messages'][0])) {
        for ($i = 0; $i < count($result['rich-messages'][0]); $i++) {
            array_push($ids, $result['rich-messages'][0][$i]['rich-message-name']);
        }
        return $ids;
    }
    if (defined('SB_WOOCOMMERCE')) {
        $ids = array_merge($ids, ['woocommerce-cart']);
    }
    return $ids;
}

function sb_get_rich_message($name, $settings = false) {
    if (in_array($name, ['registration', 'registration-tickets', 'login', 'login-tickets', 'timetable', 'articles', 'woocommerce-cart'])) {
        $title = '';
        $message = '';
        $success = '';
        switch ($name) {
            case 'registration-tickets':
            case 'registration':
                $registration_tickets = $name == 'registration-tickets';
                $active_user = sb_get_active_user();
                $registration_fields = sb_get_setting('registration-fields');
                $is_email = sb_isset($registration_fields, 'reg-email');
                $is_last_name = sb_get_setting('registration-last-name') || sb_isset($registration_fields, 'reg-last-name'); // Deprecated. Remove sb_get_setting('registration-last-name')
                $default = ['profile_image' => '', 'first_name' => '', 'last_name' => '', 'email' => '', 'password' => '', 'user_type' => 'visitor', 'details' => []];
                $user = $active_user && !sb_is_agent($active_user['user_type']) ? sb_get_user($active_user['id'], true) : $default;
                if (!$user) {
                    $user = $default;
                }
                $visitor = $user['user_type'] == 'visitor' || $user['user_type'] == 'lead';
                $settings = sb_get_setting('registration');
                $title = sb_(sb_isset($settings, 'registration-title', 'Create new account'));
                $message = sb_(sb_isset($settings, 'registration-msg', ''));
                $success = sb_(sb_isset($settings, 'registration-success', ''));
                $profile_image = sb_get_setting('registration-profile-img') || sb_isset($registration_fields, 'reg-profile-img') ? '<div id="profile_image" data-type="image" class="sb-input sb-input-image sb-profile-image"><span>' . sb_('Profile image') . '</span><div' . ($user['profile_image'] && strpos($user['profile_image'], 'media/user.svg') == false ? ' data-value="' . $user['profile_image'] . '" style="background-image:url(\'' . $user['profile_image'] . '\')"' : '') . ' class="image">' . ($user['profile_image'] && strpos($user['profile_image'], 'media/user.svg') == false ? '<i class="sb-icon-close"></i>' : '') . '</div></div>' : ''; // Deprecated. Remove sb_get_setting('registration-profile-img')
                $password = (!$registration_tickets && sb_get_setting('registration-required') == 'registration-login') || ($registration_tickets && !sb_get_setting('tickets-registration-disable-password')) ? '<div id="password" data-type="text" class="sb-input sb-input-password"><span>' . sb_('Password') . '</span><input value="' . ($user && $user['password'] ? '********' : '') . '" autocomplete="false" type="password" required></div><div id="password-check" data-type="text" class="sb-input sb-input-password"><span>' . sb_('Repeat password') . '</span><input value="' . ($user && $user['password'] ? '********' : '') . '" autocomplete="false" type="password" required></div>' : '';
                $link = $settings['registration-terms-link'] || $settings['registration-privacy-link'] ? '<div class="sb-link-area">' . sb_('By clicking the button below, you agree to our') . ' <a target="_blank" href="' . sb_isset($settings, 'registration-terms-link', $settings['registration-privacy-link']) . '">' . sb_($settings['registration-terms-link'] ? 'Terms of service' : 'Privacy Policy') . '</a>' . ($settings['registration-privacy-link'] && $settings['registration-terms-link'] ? ' ' . sb_('and') . ' <a target="_blank" href="' . $settings['registration-privacy-link'] . '">' . sb_('Privacy Policy') . '</a>' : '') . '.</div>' : '';
                $email = $is_email ? '<div id="email" data-type="text" class="sb-input sb-input-text"><span>' . sb_('Email') . '</span><input value="' . $user['email'] . '" autocomplete="off" type="email"' . (sb_isset($registration_fields, 'reg-required-email') ? ' required' : '') . '></div><div id="otp" class="sb-input"><span>' . sb_('One-time code') . '</span><input autocomplete="false" type="text"></div>' : '';
                $code = '<div class="sb-form-main sb-form">' . $profile_image . '<div id="first_name" data-type="text" class="sb-input sb-input-text"><span>' . sb_($is_last_name ? 'First name' : 'Name') . '</span><input value="' . ($visitor ? '' : $user['first_name']) . '" autocomplete="false" type="text" required></div>' . ($is_last_name ? '<div id="last_name" data-type="text" class="sb-input sb-input-text"><span>' . sb_('Last name') . '</span><input value="' . ($visitor ? '' : $user['last_name']) . '" autocomplete="false" type="text" ' . (sb_isset($registration_fields, 'reg-required-last-name') ? ' required' : '') . '></div>' : '') . $email . $password . '</div><div class="sb-form-extra sb-form">';
                $extra = [];
                if (isset($user['details'])) {
                    for ($i = 0; $i < count($user['details']); $i++) {
                        $extra[$user['details'][$i]['slug']] = $user['details'][$i]['value'];
                    }
                }
                if (sb_get_multi_setting('envato-validation', 'envato-validation-active')) {
                    $registration_fields['envato-purchase-code'] = true;
                }
                foreach ($registration_fields as $key => $value) {
                    if ($value) {
                        $key = str_replace('reg-', '', $key);
                        if (in_array($key, ['profile-img', 'last-name', 'email']) || substr($key, 0, 8) == 'required') {
                            continue;
                        }
                        $name = str_replace('-', ' ', $key);
                        $filled = (isset($extra[$name]) ? ' value="' . $extra[$name] . '"' : '');
                        $type = $type_cnt = 'text';
                        $custom_input = false;
                        $required = sb_isset($registration_fields, 'reg-required-' . $key);
                        switch ($key) {
                            case 'envato-purchase-code':
                                $required = ' required';
                                break;
                            case 'birthday':
                                $type = 'date';
                                break;
                            case 'twitter':
                            case 'linkedin':
                            case 'facebook':
                            case 'pinterest':
                            case 'instagram':
                            case 'website':
                                $type = 'url';
                                break;
                            case 'phone':
                                $type_cnt = 'select-input';
                                $custom_input = '<div class="sb-select-phone' . (sb_get_setting('phone-code') ? ' sb-single-prefix' : '') . '">' . sb_select_phone() . '</div><input' . $filled . ' autocomplete="false" type="text"' . (sb_isset($registration_fields, 'reg-required-' . $key) ? ' required' : '') . ' data-phone="true">';
                                break;
                            case 'country':
                                $type_cnt = 'select';
                                $custom_input = sb_select_html('countries');
                                break;
                            case 'language':
                                $type_cnt = 'select';
                                $custom_input = sb_select_html('languages');
                                break;
                        }
                        $code .= '<div id="' . $key . '" data-type="' . $type_cnt . '" class="sb-input sb-input-' . $type_cnt . '"><span>' . sb_(ucfirst($name)) . '</span>' . ($custom_input ? $custom_input : '<input' . $filled . ' autocomplete="false" type="' . $type . '"' . $required . '>') . '</div>';
                    }
                }
                if (sb_get_setting('registration-extra')) {
                    $additional_fields = sb_get_setting('user-additional-fields', []);
                    for ($i = 0; $i < count($additional_fields); $i++) {
                        $value = $additional_fields[$i];
                        $name = $value['extra-field-name'];
                        $filled = isset($extra[$value['extra-field-slug']]) ? ' value="' . $extra[$value['extra-field-slug']] . '"' : '';
                        if ($name) {
                            $code .= '<div id="' . $value['extra-field-slug'] . '" data-type="text" class="sb-input sb-input-text"><span>' . sb_(ucfirst($name)) . '</span><input' . $filled . ' autocomplete="false" type="text"' . ($value['extra-field-required'] ? ' required' : '') . '></div>';
                        }
                    }
                }
                $code .= '</div>' . $link . '<div class="sb-buttons"><div class="sb-btn sb-submit">' . sb_($visitor ? sb_isset($settings, 'registration-btn-text', 'Create account') : 'Update account') . '</div>' . (sb_get_setting('registration-required') == 'registration-login' && $is_email ? '<div class="sb-btn-text sb-login-area">' . sb_('Sign in instead') . '</div>' : '') . '</div>';
                break;
            case 'login-tickets':
            case 'login':
                $settings = sb_get_setting('login');
                $title = sb_(sb_isset($settings, 'login-title', 'Login'));
                $message = sb_($settings['login-msg']);
                $code = '<div class="sb-form"><div id="email" class="sb-input"><span>' . sb_('Email') . '</span><input autocomplete="false" type="email"></div><div id="password" class="sb-input"><span>' . sb_('Password') . '</span><input autocomplete="false" type="password"></div></div><div class="sb-buttons"><div class="sb-btn sb-submit-login">' . sb_('Sign in') . '</div>' . (sb_get_setting('registration-required') == 'login' ? '' : '<div class="sb-btn-text sb-registration-area">' . sb_('Create new account') . '</div>') . '</div>';
                break;
            case 'timetable':
                $settings = sb_get_settings();
                $timetable = sb_isset($settings, 'timetable', [false])[0];
                $title = $settings['chat-timetable'][0]['chat-timetable-title'][0];
                $message = $settings['chat-timetable'][0]['chat-timetable-msg'][0];
                $title = sb_t($title ? $title : 'Office hours');
                $message = sb_t($message);
                $code = '<div class="sb-timetable" data-offset="' . sb_get_setting('timetable-utc') . '">';
                if ($timetable) {
                    foreach ($timetable as $day => $hours) {
                        if ($hours[0][0]) {
                            $code .= '<div><div>' . sb_(ucfirst($day)) . '</div><div data-time="' . $hours[0][0] . '|' . $hours[1][0] . '|' . $hours[2][0] . '|' . $hours[3][0] . '"></div></div>';
                        }
                    }
                }
                $code .= '<span></span></div>';
                break;
            case 'articles':
                $articles_title = sb_get_setting('articles-title');
                $articles_button_link = sb_get_setting('articles-button-link');
                $code = '<div class="sb-dashboard-articles"><div class="sb-title">' . sb_t($articles_title ? $articles_title : 'Help Center') . '</div><div class="sb-input sb-input-btn"><input placeholder="' . sb_('Search for articles...') . '" autocomplete="off"><div class="sb-submit-articles sb-icon-search"></div></div><div class="sb-articles">';
                $articles = sb_get_articles(false, 2, false, false, sb_get_user_language(), true);
                for ($i = 0; $i < count($articles); $i++) {
                    if (!empty($articles[$i])) {
                        $code .= '<div data-id="' . $articles[$i]['id'] . '"><div>' . $articles[$i]['title'] . '</div><span>' . $articles[$i]['content'] . '</span></div>';
                    }
                }
                $code .= '</div><div class="sb-btn sb-btn-all-articles"' . ($articles_button_link ? ' onclick="document.location.href = \'' . $articles_button_link . '\'"' : '') . '>' . sb_('All articles') . '</div></div>';
                break;
            case 'woocommerce-cart':
                $code = sb_woocommerce_rich_messages($name);
                break;
        }
        return ($title ? '<div class="sb-top">' . $title . '</div>' : '') . ($message ? '<div class="sb-text">' . $message . '</div>' : '') . $code . '<div data-success="' . $success . '" class="sb-info"></div>';
    } else {
        $result = sb_get_external_setting('rich-messages');
        if (is_array($result)) {
            $rich_messages = sb_isset($result, 'rich-messages')[0];
            if (is_array($rich_messages)) {
                for ($i = 0; $i < count($rich_messages); $i++) {
                    $item = $result['rich-messages'][0][$i];
                    if ($item['rich-message-name'] == $name) {
                        return $item['rich-message-content'];
                    }
                }
            }
        }
    }
    return false;
}

function sb_rich_value($value, $merge_fields = true, $translate = true, $shortcodes = false) {
    if ($translate) {
        $value = sb_t($value);
    }
    if (!$shortcodes) {
        $value = str_replace('"', '\'', strip_tags($value));
        $value = str_replace(['[', ']'], '', $value);
        $value = str_replace([PHP_EOL, "\r", "\n"], "\n", $value);
    }
    return trim($merge_fields ? sb_merge_fields($value) : $value);
}

function sb_get_shortcode($message, $name = false, $merge_field = false) {
    $separator = $merge_field ? ['{', '}'] : ['[', ']'];
    $response = [];
    $position = false;
    $is_name = $name;
    if (!is_string($message) || strpos($message, $separator[0]) === false) {
        return [];
    }
    if (!$name) {
        if ($merge_field) {
            preg_match_all('/\{(.*?)\}/', $message, $matches);
            $name = empty($matches[1]) ? false : $matches[1][0];
            if (strpos($name, ' ')) {
                $name = substr($name, 0, strpos($name, ' '));
            }
        } else {
            $shortcode_names = sb_get_rich_messages_ids(false);
            for ($i = 0; $i < count($shortcode_names); $i++) {
                $position = strpos($message, $separator[0] . $shortcode_names[$i]);
                if ($position !== false) {
                    $name = $shortcode_names[$i];
                    break;
                }
            }
        }
        if (!$name) {
            return [];
        }
    }
    $position = $position ? $position : strpos($message, $separator[0] . $name);
    if ($position !== false) {
        $code = substr($message, $position);
        $code = substr($code, 0, strpos($code, $separator[1]) + 1);
        $item = ['shortcode_name' => $name, 'shortcode' => $code];
        $values = [];
        if (preg_match_all('/([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*\'([^\']*)\'(?:\s|$)|([\w-]+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|\'([^\']*)\'(?:\s|$)|(\S+)(?:\s|$)/', substr($code, 1, -1), $values, PREG_SET_ORDER)) {
            for ($i = 0; $i < count($values); $i++) {
                if (count($values[$i]) == 3 && !empty($values[$i][1]) && !empty($values[$i][2])) {
                    $item[$values[$i][1]] = $values[$i][2] === 'false' ? false : ($values[$i][2] === 'true' ? true : $values[$i][2]);
                }
            }
        }
        array_push($response, $item);
        if (!$is_name) {
            $message_2 = str_replace($code, '', $message);
            $shortcode_2 = sb_get_shortcode($message_2, false, $merge_field);
            if (!empty($shortcode_2)) {
                $response = array_merge($response, $shortcode_2);
            }
        }
    }
    return $is_name && !empty($response) ? $response[0] : $response;
}

function sb_execute_bot_message($name, $conversation_id, $last_user_message = false, $check = true) {
    $valid = false;
    $settings = false;
    $message = '';
    $is_check = $conversation_id == 'check';
    $delay = false;
    if (!$is_check && sb_conversation_security_error($conversation_id)) {
        return sb_error('security-error', 'sb_execute_bot_message');
    }
    switch ($name) {
        case 'offline':
            $settings = sb_get_setting('chat-timetable');
            $valid = $settings['chat-timetable-active'] && (!sb_office_hours() || (!$settings['chat-timetable-agents'] && !sb_agents_online()));
            break;
        case 'follow_up':
            $settings = sb_get_block_setting('follow');
            $valid = $settings;
            break;
    }
    if ($is_check) {
        return $valid;
    }
    if ($valid && (!$check || ($conversation_id && $name == 'offline') || sb_db_get('SELECT COUNT(*) AS `count` FROM sb_messages WHERE payload LIKE "{\"' . $name . '_message%" AND creation_time > "' . sb_gmt_now(864000) . '" AND conversation_id = ' . sb_db_escape($conversation_id, true))['count'] == 0)) {
        switch ($name) {
            case 'offline':
                $message = sb_get_multi_setting('chat-timetable', 'chat-timetable-title');
                $message = ($message ? '*' . sb_($message) . '*' . PHP_EOL : '') . sb_(sb_get_multi_setting('chat-timetable', 'chat-timetable-msg'));
                if ($conversation_id) {
                    $messages = sb_isset(sb_get_conversation(false, $conversation_id), 'messages');
                    $count = $messages ? count($messages) - 1 : 0;
                    for ($i = $count; $i > -1; $i--) {
                        if (sb_is_agent($messages[$i], true) || $messages[$i]['message'] == $message) {
                            if ($messages[$i]['message'] == $message) {
                                return false;
                            }
                            break;
                        }
                    }
                }
                break;
            case 'follow_up':
                $message = '[email id="sb-follow-up" title="' . sb_rich_value($settings['title']) . '" message="' . sb_rich_value($settings['message']) . '" placeholder="' . sb_rich_value($settings['placeholder']) . '" name="' . $settings['name'] . '" last-name="' . $settings['last-name'] . '" phone="' . $settings['phone'] . '" phone-required="' . $settings['phone-required'] . '" success="' . sb_rich_value($settings['success']) . '"]';
                $delay = $settings['delay'];
                break;
        }
        if ($delay) {
            sleep(intval($delay) / 1000);
        }
        $message_id = sb_send_message(sb_get_bot_id(), $conversation_id, $message, [], false, [$name . '_message' => true, 'preview' => $last_user_message ? $last_user_message : $message])['id'];
        return ['message' => $message, 'attachments' => [], 'id' => $message_id, 'settings' => $settings];
    }
    return false;
}

function sb_is_rich_message($string) {
    $ids = sb_get_rich_messages_ids();
    for ($i = 0; $i < count($ids); $i++) {
        if (strpos($string, '[' . $ids[$i]) !== false) {
            return true;
        }
    }
    return false;
}

/*
 * -----------------------------------------------------------
 * MESSAGING PLATFORMS
 * -----------------------------------------------------------
 *
 * 1. Manage the messaging platforms features
 * 2. Send messages to the messaging platforms
 * 3. Send a text message
 * 4. Remove Support Board global text formatting
 *
 */

function sb_messaging_platforms_functions($conversation_id, $message, $attachments, $user, $source) {
    if (is_numeric($user)) {
        $user = sb_get_user($user);
        if (!$user) {
            return sb_error('user-not-found', 'sb_messaging_platforms_functions');
        }
    }
    if (is_string($source)) {
        $source = ['source' => $source];
    }
    if (!$attachments) {
        $attachments = [];
    }
    $last_message = sb_db_get('SELECT message FROM sb_messages WHERE message <> "" AND message <> "' . sb_db_escape($message) . '" AND conversation_id = ' . $conversation_id . ' ORDER BY id DESC LIMIT 1');
    $user_id = $user['id'];
    $source_name = $source['source'];
    $bot_messages = true;
    $human_takeover = false;
    $skip_chatbot = false;
    $slack = defined('SB_SLACK') && sb_slack_can_send($conversation_id) ? [$user['id'], sb_get_user_name($user), $user['profile_image']] : false;
    $dialogflow_active = sb_chatbot_active(true, false);
    $open_ai_active = sb_chatbot_active(false, true);
    $message_id = false;
    $source['user_id'] = $user_id;
    $source['id'] = $conversation_id;

    // Rich messages
    if ($last_message) {
        $last_message = $last_message['message'];
        $shortcodes = sb_get_shortcode($last_message);
        for ($j = 0; $j < count($shortcodes); $j++) {
            $shortcode = $shortcodes[$j];
            switch ($shortcode['shortcode_name']) {
                case 'phone':
                case 'email':
                    if (!in_array($source_name, ['em', 'tm'])) {
                        $valid = false;
                        $is_email = $shortcode['name'] == 'email';
                        $filter = $is_email ? ['@', 'email'] : (strpos($message, '+') !== false ? ['+', 'phone'] : false);
                        if ($filter) {
                            $words = explode(' ', $message);
                            for ($i = 0; $i < count($words); $i++) {
                                if (strpos($words[$i], $filter[0]) !== false) {
                                    $value = trim($words[$i]);
                                    if (substr($value, -1) == '.')
                                        $value = substr($value, 0, -1);
                                    if (strlen($value) > 3 && (($is_email && strpos($value, '.')) || (!$is_email && is_numeric(substr($value, 1))))) {
                                        sb_update_user_value($user_id, $filter[1], $value);
                                        if (!empty($shortcode['success'])) {
                                            if ($is_email && !empty($shortcode['phone']) && $source_name != 'wa' && !sb_get_user_extra($user_id, 'phone')) {
                                                $message_new = '[phone message="' . sb_('Enter your phone number') . '" success="' . sb_t($shortcode['success']) . '"]';
                                            } else {
                                                $message_new = sb_t(sb_merge_fields($shortcode['success']));
                                            }
                                            $message_id = sb_send_message(sb_get_bot_id(), $conversation_id, $message_new, [], -1, ['event' => 'update-user'])['id'];
                                            sb_messaging_platforms_send_message($message_new, $source, $message_id);
                                            if ($slack)
                                                sb_send_slack_message($slack[0], $slack[1], $slack[2], $message_new, [], $conversation_id);
                                        }
                                        $valid = true;
                                    }
                                }
                            }
                        }
                        if (!$valid && !empty($shortcode['required-messaging-apps'])) {
                            $message_id = sb_send_message(sb_get_bot_id(), $conversation_id, $last_message)['id'];
                            sb_messaging_platforms_send_message($last_message, $source, $message_id);
                            if ($slack) {
                                sb_send_slack_message($slack[0], $slack[1], $slack[2], $last_message, [], $conversation_id);
                            }
                        }
                        $skip_chatbot = true;
                    } else {
                        $bot_messages = false;
                    }
                    break;
            }
        }
    }

    // Dialogflow and OpenAI
    if (($dialogflow_active || $open_ai_active) && !sb_get_setting(($source_name == 'ig' ? 'fb' : $source_name) . '-disable-chatbot')) {
        $bot_messages = false;
        $response = false;
        if (!$skip_chatbot && (!sb_get_setting('dialogflow-timetable') || !sb_office_hours())) {
            $voice_message = false;
            $messages = [];
            for ($i = 0; $i < count($attachments); $i++) {
                if (strpos($attachments[$i][0], 'voice_message')) {
                    $voice_message = $attachments[$i][1];
                    break;
                }
            }
            if ($dialogflow_active) {
                $response = sb_dialogflow_message($conversation_id, $message, -1, [sb_get_user_language($user_id)], $attachments, '', false, false, false, $voice_message);
                $messages = sb_isset($response, 'messages', []);
                $human_takeover = isset($response['human_takeover']);
            } else {
                $response_open_ai = sb_open_ai_message($message, false, false, $conversation_id, ['messaging-app' => $source_name], $voice_message, $attachments);
                if ($response_open_ai[0]) {
                    $messages = is_string($response_open_ai[1]) ? [['message' => $response_open_ai[1], 'attachments' => sb_isset($response_open_ai, 6)]] : (isset($response_open_ai[1]['message']) ? [$response_open_ai[1]] : sb_isset($response_open_ai[1], 'messages', is_array($response_open_ai[1]) ? $response_open_ai[1] : []));
                    $human_takeover = !empty($response_open_ai[3]);
                }
            }
            for ($i = 0; $i < count($messages); $i++) {
                $message_text = sb_isset($messages[$i], 'message', '');
                $message_attachments = sb_isset($messages[$i], 'attachments', []);
                $payload = sb_isset($messages[$i], 'payload', []);
                if (isset($payload['rich-message'])) {
                    $message_text .= $payload['rich-message'];
                }
                if ($message_text || $message_attachments) {
                    if (($source_name == 'tm' || $source_name == 'em') && (!empty($response) || !sb_dialogflow_is_unknow($response)) && sb_open_ai_is_valid($message_text)) {
                        if ($source_name == 'em') {
                            sb_email_create($user['id'], sb_get_setting('bot-name', 'Chatbot'), sb_get_setting('bot-image'), $message_text, $message_attachments, false, $conversation_id);
                        } else {
                            sb_send_sms($message_text, $source['phone'], true, $conversation_id, $message_attachments);
                        }
                        sb_send_agents_notifications($message_text, false, $conversation_id, $message_attachments, $user);
                    }
                    $delay = sb_get_setting('dialogflow-bot-delay');
                    if ($delay) {
                        sleep(intval($delay) / 1000);
                    }
                    sb_messaging_platforms_send_message($message_text, $source, sb_isset($messages[$i], 'id'), $message_attachments);
                }
                if ($payload) {
                    $source['attachments'] = $attachments;
                    sb_dialogflow_payload($payload, $conversation_id, $message, $source);
                }
                if ($slack) {
                    sb_send_slack_message($slack[0], $slack[1], $slack[2], $messages[$i]['message'], sb_isset($messages[$i], 'attachments', []), $conversation_id);
                }
            }
        }
    }

    // Bot messages
    if ($bot_messages || $human_takeover) {
        $is_new_conversation = !empty($source['new_conversation']);
        $bot_messages = ['offline', 'welcome'];
        if (!sb_get_multi_setting('follow-message', 'follow-disable-channels') && (!$is_new_conversation || $source_name != 'wa' || !in_array(sb_get_setting('registration-required'), ['registration', 'registration-login']))) {
            array_push($bot_messages, 'follow_up');
        }
        if (!sb_get_multi_setting('privacy', 'privacy-disable-channels')) {
            array_push($bot_messages, 'privacy');
        }
        $count = count($bot_messages);
        $last_user_message = $count ? sb_isset(sb_get_last_message($conversation_id, $message, $user_id), 'message') : false;
        for ($i = 0; $i < $count; $i++) {
            $bot_message = $i == 0 || empty($user['email']) ? sb_execute_bot_message($bot_messages[$i], $conversation_id, $last_user_message) : false;
            $message_2 = false;
            if ($i == 3 && $is_new_conversation && sb_get_multi_setting('welcome-message', 'welcome-active') && (!sb_get_multi_setting('welcome-message', 'welcome-disable-office-hours') || sb_office_hours())) {
                $message_2 = sb_get_multi_setting('welcome-message', 'welcome-msg');
            }
            if ($i == 4 && $is_new_conversation && sb_get_multi_setting('privacy', 'privacy-active')) {
                $message_2 = sb_get_multi_setting('privacy', 'privacy-msg');
            }
            if ($message_2) {
                $bot_message = ['id' => sb_send_message(sb_get_bot_id(), $conversation_id, $message_2)['id'], 'message' => $message_2];
            }
            if ($bot_message) {
                sb_messaging_platforms_send_message($bot_message['message'], $source, $bot_message['id']);
                if ($slack) {
                    sb_send_slack_message($slack[0], $slack[1], $slack[2], $bot_message['message'], [], $conversation_id);
                }
            }
        }
    }

    // Slack
    if ($slack) {
        sb_send_slack_message($slack[0], $slack[1], $slack[2], $message, $attachments, $conversation_id);
    }
    return $human_takeover ? 'human_takeover' : true;
}

function sb_messaging_platforms_send_message($message, $conversation, $message_id = false, $attachments = []) {
    $conversation = is_numeric($conversation) ? sb_db_get('SELECT id, user_id, source, extra FROM sb_conversations WHERE id = ' . $conversation) : $conversation;
    $platform_value = sb_isset($conversation, 'platform_value');
    $user_id = $conversation['user_id'];
    if (defined('SB_DIALOGFLOW')) {
        $message = sb_google_translate_auto($message, $user_id);
    }
    $message = preg_replace('/\[action.*?\]/', '', $message);
    switch ($conversation['source']) {
        case 'ig':
        case 'fb':
            return sb_messenger_send_message($platform_value ? $platform_value : sb_get_user_extra($user_id, 'facebook-id'), isset($conversation['page_id']) ? $conversation['page_id'] : $conversation['extra'], $message, $attachments, $message_id);
        case 'wa':
            return sb_whatsapp_send_message($platform_value ? $platform_value : sb_get_user_extra($user_id, 'phone'), $message, $attachments, $conversation['extra']);
        case 'tg':
            return sb_telegram_send_message($platform_value ? $platform_value : sb_isset($conversation, 'chat_id', $conversation['extra']), $message, $attachments, $conversation['id']);
        case 'wc':
            return sb_wechat_send_message($platform_value ? $platform_value : sb_get_user_extra($user_id, 'wechat-id'), $message, $attachments);
        case 'tw':
            return sb_twitter_send_message($platform_value ? $platform_value : sb_get_user_extra($user_id, 'twitter-id'), $message, $attachments);
        case 'vb':
            return sb_viber_send_message($platform_value ? $platform_value : sb_get_user_extra($user_id, 'viber-id'), $message, $attachments);
        case 'za':
            return sb_zalo_send_message($platform_value ? $platform_value : sb_get_user_extra($user_id, 'zalo-id'), $message, $attachments);
        case 'ln':
            return sb_line_send_message($platform_value ? $platform_value : sb_get_user_extra($user_id, 'line-id'), $message, $attachments, $conversation['id']);
    }
    return false;
}

function sb_send_sms($message, $to, $template = true, $conversation_id = true, $attachments = false) {
    $settings = sb_get_setting('sms');
    $to_agents = $to == 'agents' || $to == 'all-agents' || strpos($to, 'department-') !== false;

    // Retrive phone number
    if ($to_agents) {
        $phones = sb_db_get('SELECT A.id, value FROM sb_users A, sb_users_data B WHERE A.id = B.user_id AND (user_type = "agent" OR user_type = "admin") AND slug = "phone"' . ($to == 'agents' ? ' AND (department IS NULL OR department = "")' : (strpos($to, 'department-') !== false ? ' AND department = ' . substr($to, 11) : '')), false);
        $online_agents_ids = sb_get_online_user_ids(true);
        for ($i = 0; $i < count($phones); $i++) {
            if (!in_array($phones[$i]['id'], $online_agents_ids)) {
                sb_send_sms($message, $phones[$i]['value'], $template, $conversation_id, $attachments);
            }
        }
        return false;
    } else if (strpos($to, '+') === false && substr($to, 0, 2) != '00') {
        $to = sb_get_user_extra($to, 'phone');
        if (empty($to)) {
            return false;
        }
    }

    // Recipient user details, security, and merge fields
    $user = sb_get_user_by('phone', $to);
    $user_id = sb_isset($user, 'id');
    if (!sb_is_agent() && !sb_is_agent($user) && sb_get_active_user_ID() != $user_id && empty($GLOBALS['SB_FORCE_ADMIN'])) {
        return sb_error('security-error', 'sb_send_sms');
    }

    // Shortcodes
    $shortcodes = sb_get_shortcode($message);
    for ($j = 0; $j < count($shortcodes); $j++) {
        $shortcode = $shortcodes[$j];
        $shortcode_name = $shortcode['shortcode_name'];
        $message = trim(str_replace($shortcode['shortcode'], '', $message) . (isset($shortcode['title']) ? sb_($shortcode['title']) : '') . PHP_EOL . sb_(sb_isset($shortcode, 'message', '')));
        switch ($shortcode_name) {
            case 'slider-images':
            case 'slider':
            case 'card':
                if ($shortcode_name == 'slider-images') {
                    $images = explode(',', $shortcode['images']);
                    for ($i = 0; $i < count($images); $i++) {
                        $message .= PHP_EOL . $images[$i];
                    }
                } else {
                    $index = $shortcode_name == 'slider' ? 1 : 0;
                    while (isset($shortcode['image' . ($index ? '-' . $index : '')])) {
                        $suffix = $index ? '-' . $index : '';
                        $link = sb_isset($shortcode, 'link' . $suffix);
                        $description = sb_isset($shortcode, 'description' . $suffix);
                        $message .= PHP_EOL . $shortcode['header' . $suffix] . ($description ? PHP_EOL . $description : '') . ($link ? PHP_EOL . $link : '');
                        $index++;
                    }
                }
                break;
            case 'list-image':
            case 'list':
                $index = 0;
                if ($shortcode_name == 'list-image') {
                    $shortcode['values'] = str_replace('://', '', $shortcode['values']);
                    $index = 1;
                }
                $values = explode(',', $shortcode['values']);
                if (strpos($values[0], ':')) {
                    for ($i = 0; $i < count($values); $i++) {
                        $value = explode(':', $values[$i]);
                        $message .= PHP_EOL . '• *' . trim($value[$index]) . '* ' . trim($value[$index + 1]);
                    }
                } else {
                    for ($i = 0; $i < count($values); $i++) {
                        $message .= PHP_EOL . '• ' . trim($values[$i]);
                    }
                }
                break;
            case 'select':
            case 'buttons':
            case 'chips':
                $values = explode(',', $shortcode['options']);
                for ($i = 0; $i < count($values); $i++) {
                    $message .= PHP_EOL . '• ' . trim($values[$i]);
                }
                break;
            case 'share':
            case 'button':
            case 'articles':
                $message .= PHP_EOL . $shortcode['link'];
                break;
            case 'video':
                $message .= PHP_EOL . ($shortcode['type'] == 'youtube' ? 'https://www.youtube.com/embed/' : 'https://player.vimeo.com/video/') . $shortcode['id'];
                break;
            case 'image':
                $message .= PHP_EOL . $shortcode['url'];
                break;
        }
    }
    $message = trim($message);
    if (defined('SB_DIALOGFLOW')) {
        $message = sb_google_translate_auto($message, $user_id);
    }
    $message_template = $template ? sb_t($settings[(sb_is_agent() || !empty($GLOBALS['SB_FORCE_ADMIN'])) && !$to_agents ? 'sms-message-user' : 'sms-message-agent']) : false;
    $message = $message_template ? str_replace('{message}', $message, $message_template) : $message;
    $message = str_replace(['{conversation_url_parameter}', '{recipient_name}', '{sender_name}', '{recipient_email}', '{sender_email}'], [$conversation_id && $user ? ('?conversation=' . $conversation_id . '&token=' . $user['token']) : '', sb_get_user_name($user), sb_get_user_name(), sb_isset($user, 'email'), sb_isset(sb_get_active_user(), 'email', '')], sb_merge_fields($message));

    // Send the SMS
    $message = sb_clear_text_formatting(strip_tags($message));
    $query = ['Body' => $message, 'From' => $settings['sms-sender'], 'To' => $to];
    $query_curl = $query;
    if ($attachments) {
        $mime_types = ['jpeg', 'jpg', 'png', 'gif'];
        for ($i = 0; $i < count($attachments); $i++) {
            $attachment = is_array($attachments[$i]) ? $attachments[$i][1] : $attachments[$i];
            if (in_array(pathinfo($attachment, PATHINFO_EXTENSION), $mime_types)) {
                $query['MediaUrl' . $i] = $attachment;
            } else {
                $message .= PHP_EOL . PHP_EOL . $attachment;
            }
        }
        $query['Body'] = $message;
        $query_curl = http_build_query($query);
        if (strpos($query_curl, 'MediaUrl')) {
            $query_curl = str_replace(['MediaUrl0', 'MediaUrl1', 'MediaUrl2', 'MediaUrl3', 'MediaUrl4', 'MediaUrl5', 'MediaUrl6', 'MediaUrl7', 'MediaUrl8', 'MediaUrl9'], 'MediaUrl', $query_curl);
        }
    }
    $response = sb_curl('https://api.twilio.com/2010-04-01/Accounts/' . $settings['sms-user'] . '/Messages.json', $query_curl, ['Authorization: Basic  ' . base64_encode($settings['sms-user'] . ':' . $settings['sms-token'])]);
    sb_webhooks('SBSMSSent', array_merge($query, $response));
    return $response;
}

function sb_messaging_platforms_text_formatting($message) {
    preg_match_all('/#sb-[a-zA-Z0-9-_]+/', $message, $matches);
    if (!empty($matches[0])) {
        for ($i = 0; $i < count($matches[0]); $i++) {
            $message = str_replace($matches[0][$i], '', $message);
        }
    }
    return $message;
}

?>