| Current Path : /var/www/html/components/com_jchat/Model/ |
| Current File : /var/www/html/components/com_jchat/Model/StreamModel.php |
<?php
namespace JExtstore\Component\JChat\Site\Model;
/**
* @package JCHAT::STREAM::components::com_jchat
* @subpackage models
* @author Joomla! Extensions Store
* @copyright (C) 2024 - Joomla! Extensions Store
* @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
*/
defined( '_JEXEC' ) or die( 'Restricted access' );
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Filter\OutputFilter;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Language\Multilanguage;
use JExtstore\Component\JChat\Administrator\Framework\Model as JChatModel;
use JExtstore\Component\JChat\Administrator\Framework\Exception as JChatException;
use JExtstore\Component\JChat\Administrator\Framework\Helpers\Messages as JChatHelpersMessages;
use JExtstore\Component\JChat\Administrator\Framework\Helpers\Users as JChatHelpersUsers;
use JExtstore\Component\JChat\Administrator\Framework\Html\Menu as JChatHtmlMenu;
use JExtstore\Component\JChat\Administrator\Framework\Helpers\Language as JChatHelpersLanguage;
use JExtstore\Component\JChat\Administrator\Framework\Helpers\Mailer as JChatMailer;
use JExtstore\Component\JChat\Administrator\Framework\Http;
use JExtstore\Component\JChat\Administrator\Framework\Http\Transport\Socket;
use JExtstore\Component\JChat\Administrator\Framework\Http\Transport\Curl;
/**
* Stream model public responsibilities interface
* The entity to perform CRUD operation on is the Stream
* It supports special get/store/delete responsibilities to be a
* more generic stream resource for chat service
*
* @package JCHAT::STREAM::components::com_jchat
* @subpackage models
* @since 1.0
*/
interface IStreamModel {
/**
* Get the Data on read from Stream
* @access public
* @return array
*/
public function getChatData();
/**
* Detect if live support mode is active and returns query chunks to
* filter users/messages if users is not a chat admins
*
* @access private
* @return array
*/
public function getQueryLiveSupport();
/**
* Translate chat access levels into user groups and generates filtering query
* accordingly for the users taken into account by the buddylist, thus avoiding
* show disabled users into chat of enabled users refreshing session lifetime during navigation
*
* @access private
* @return array
*/
public function getQueryAccessLevels();
/**
* Filter the buddylist based on the current user groups belonging
* Users will be be able to chat only with users in the same users groups
*
* @access public
* @return array
*/
public function getQueryMyUsersGroups($joinTable = 'sess.userid');
/**
* Get query parts needed for SELECT & JOIN
* tables for database integration component
*
* @access private
* @param string $queryType
* @return array
*/
public function getQueryParts($queryType);
/**
* Write user status on Stream
*
* @access public
* @param string $status
* @return array
*/
public function storeUserStatus($fieldName, $fieldValue);
/**
* Write user Skype ID on Stream
*
* @access public
* @param int $skypeID
* @return array
*/
public function storeUserStateFromRequest($statusVarName, $statusVarValue);
/**
* Store banning state for current session id user
*
* @access public
* @return array
*/
public function storeBannedUsersState($bannedUserInfo);
/**
* Write private message on Stream
*
* @access public
* @param string $to
* @param int $tologged
* @param string $message
* @param Object $mailer
* @return array
*/
public function storePrivateMessage($to, $tologged, $message, $mailer);
/**
* Write group message on Stream
*
* @access public
* @param int $to
* @param string $message
* @return array
*/
public function storeGroupMessage($to, $message);
/**
* Add a new chatroom to the database
*
* @access public
* @param string $roomName
* @param string $roomDescription
* @param int $roomAccess
* @param string $roomMenuitems
* @return array
*/
public function storeNewChatroom($roomName, $roomDescription, $roomAccess, $roomMenuItems);
/**
* Delete conversation from session
*
* @access public
* @param int $from
* @return array
*/
public function deleteConversation($from);
/**
* Delete chatroom from the frontend stream
*
* @access public
* @param int $chatroomID
* @return array
*/
public function deleteChatroom($chatroomID);
/**
* Retrieve guest info user informations, usually stored by guest activation form
*
* @access public
* @param string $session_id
* @return array
*/
public function getInfoGuest($session_id);
/**
* Load chatrooms from DB using caching system
*
* @access private
* @param int $menuItemid
* @return array
*/
public function loadChatRooms($menuItemid);
/**
* Get user profile based on integration type
*
* @access public
* @param int $id
* @param string $name
* @param Object $cParams
* @return string
*/
public function formatUserProfileLink($id, $name, $cParams);
/**
* Retrieve old messages based on time period and private conversation
* between to logged in and registered users
*
* @access public
* @param int $fromLoggedID
* @param string $fromUserID
* @param string $timePeriod
* @param int $minMessageId
* @return array
*/
public function fetchHistoryMessages($fromLoggedID, $fromUserID, $timePeriod, $minMessageId);
}
/**
* Main stream class concrete implementation
*
* @package JCHAT::STREAM::components::com_jchat
* @subpackage models
* @since 1.0
*/
class StreamModel extends JChatModel implements IStreamModel {
/**
* Response aray
* @access private
* @var array
*/
private $response;
/**
* Main private messages
* @access private
* @var array
*/
private $messages;
/**
* Public group messages
* @access private
* @var array
*/
private $wallMessages;
/**
* User Object
* @access private
* @var Object &
*/
private $myUser;
/**
* User chatroom ID if any
* @access private
* @var Object &
*/
private $myChatRoom;
/**
* User session table Object
* @access private
* @var Object &
*/
private $userSessionTable;
/**
* Type of social extension integrated if any from main config params
* @access private
* @var string
*/
private $integratedExtensions;
/**
* Monitor typing status changes from users interaction with chatbox
* @access private
* @var string
*/
private $typingStatusChanged;
/**
* Keep track of the target typing user to that user is writing
* @access private
* @var string
*/
private $typingTo;
/**
* Discard operations if typing is not enabled
* @access private
* @var string
*/
private $typingEnabled;
/**
* Sess SQL query for the client id
* @access private
* @var string
*/
private $sessClientId;
/**
* Session SQL query for the client id
* @access private
* @var string
*/
private $sessionClientId;
/**
* Store the current state of the meeting feature
* @access private
* @var boolean
*/
private $meetingsFeatureEnabled;
/**
* Store the current state of the livestreaming feature based on menu items
* @access private
* @var boolean
*/
private $livestreamingFeatureEnabled;
/**
* Params Object
* @access protected
* @var Object &
*/
protected $componentParams;
/**
* Convert time to day/hours/minutes
* @access private
* @param int $time
* @return string
*/
private function convertToDaysHoursMins($time) {
settype($time, 'integer');
$time2Display = null;
if ($time < 0) {
return;
}
// case: show years
$years = floor(((($time/60)/60)/24)/365);
if($years > 0) {
$time2Display = $years . Text::_('COM_JCHAT_YEARS');
}
// case: show days
$days = floor((($time/60)/60)/24);
if($days > 0 && $days < 365) {
$time2Display = $days . Text::_('COM_JCHAT_DAYS');
}
// case: show hours
$hours = floor(($time/60)/60);
if($hours > 0 && $hours < 24) {
$time2Display = $hours . Text::_('COM_JCHAT_HOURS');
}
// case: show minutes
$minutes = floor($time/60);
if($minutes > 0 && $minutes < 60) {
$time2Display = $minutes . Text::_('COM_JCHAT_MINUTES');
}
// case: show seconds
$seconds = $time;
if($seconds > 0 && $seconds < 60) {
$time2Display = $seconds . Text::_('COM_JCHAT_SECONDS');
}
return $time2Display;
}
/**
* Si occupa di controllare se esistono nuovi MSGFILE presenti nel database con status = 1
* che non sono stati refreshati in sessione e li pone nella response['downloads'] memorizzandoli
* separatamente in sessione per evitare doppie notifiche e aggiornamenti
*
* @access private
* @return void
*/
private function refreshMsgFileSessionStatus() {
$query = "SELECT id, " . $this->dbInstance->quoteName('to') . " FROM #__jchat" .
"\n WHERE type=" . $this->dbInstance->quote('file') .
"\n AND status = 1" .
"\n AND " . $this->dbInstance->quoteName('from') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id);
$this->dbInstance->setQuery($query);
$msgFiles = $this->dbInstance->loadObjectList();
// Gestiamo il session array downloads gi� notificati
if(!isset($this->sessionName['jchat_notified_downloads'])) {
$this->sessionName['jchat_notified_downloads'] = array();
}
if(is_array($msgFiles) && count($msgFiles)) {
foreach ($msgFiles as $msgFile) {
if(!array_key_exists($msgFile->id, $this->sessionName['jchat_notified_downloads'])) {
$conversation2Refresh = &$this->sessionName['jchat_user_' . $msgFile->to];
if(is_array($conversation2Refresh) && count($conversation2Refresh)) {
$conversation2Refresh[$msgFile->id]['status'] = $this->sessionName['jchat_user_' . $msgFile->to][$msgFile->id]['status'] = 1;
$this->sessionName['jchat_notified_downloads'][$msgFile->id] = true;
$this->response['downloads'][] = array($msgFile->to, $msgFile->id);
}
}
}
}
}
/**
* Get user profile based on integration type
*
* @access private
* @param int $id
* @param string $name
* @param Object $cParams
* @return string
*/
private function getUserProfileLink($id, $name, $cParams) {
// User id required
if(!$id) {
return null;
}
if(!$cParams->get('3pdintegration', null) && !$cParams->get('joomlauser_profilelink', 0)) {
return null;
}
// Get list of current chatrooms available
$cachable = $cParams->get('caching', false);
if($cachable) {
// By default callback handler
$cache = $this->getExtensionCache();
$profileLink = $cache->get(array($this, 'formatUserProfileLink'), array($id, $name, $cParams));
} else {
$profileLink = $this->formatUserProfileLink($id, $name, $cParams);
}
return $profileLink;
}
/**
* Retrieve TURN server credentials from Twilio service based on $sid and $token registered account
*
* @access private
* @param string $sid
* @param string $token
* @return mixed, false if error or the retrieved object including iceServers property
*/
private function getTURNServer($sid, $token) {
if(version_compare(PHP_VERSION, '5.3', '>=')) {
// Composer autoloader
require_once JPATH_COMPONENT_ADMINISTRATOR . '/Framework/composer/autoload_real.php';
} else {
return false;
}
try {
// Initialize the client
$client = new \Twilio\Rest\Client($sid, $token);
//Creates a token
$tokenObject = $client->tokens->create();
} catch (\Exception $e) {
return false;
}
return $tokenObject;
}
/**
* Translates all the parameters of type ACL group including child groups
*
* @access private
* @param array $aclParameters
* @return boolean
*/
private function translatesACLParameters($aclParameters) {
// Iterate over ACl parameters
foreach ($aclParameters as $aclParameter) {
$aclParamValue = $this->componentParams->get($aclParameter, array(0));
$totalParamGroups = $aclParamValue;
// Ensure that the ACL parameter has no 'All groups' option selected
if(is_array($aclParamValue) && !in_array(0, $aclParamValue, false)) {
// Cycle on all the group selected and retrieve the child groups
foreach ($aclParamValue as $aclParamGroup) {
$totalParamGroups = array_merge($totalParamGroups, JChatHelpersUsers::getChildGroups($this->dbInstance, $aclParamGroup));
}
// Remove duplicates
$totalParamGroups = array_unique($totalParamGroups);
}
// Final reassignment to the component params array, with the $totalparamGroups augmented with childs
$this->componentParams->set($aclParameter, $totalParamGroups);
}
return true;
}
/**
* Check if the current user has permission to access the ChatGPT bot
* based on the allow_chatgpt_bot ACL group-based permission setting
*
* @access private
* @return boolean True if the user has access, false otherwise
*/
private function hasAccessToChatGPTBot() {
$allowChatGPTGroups = $this->componentParams->get('allow_chatgpt_bot', array(0));
// If "All groups" (value 0) is selected, everyone has access
if (!is_array($allowChatGPTGroups) || in_array(0, $allowChatGPTGroups, false)) {
return true;
}
// Check if the user belongs to one of the allowed groups
$userGroups = $this->myUser->getAuthorisedGroups();
$intersectResult = array_intersect($userGroups, $allowChatGPTGroups);
return (bool)count($intersectResult);
}
/**
* Build the enhanced AI system prompt from all configuration parameters
*
* @access private
* @param string $baseConversationArgument The base conversation argument/system prompt
* @return string The complete system prompt
*/
private function buildAISystemPrompt($baseConversationArgument) {
$promptParts = array();
// 1. Base conversation argument
if (!empty($baseConversationArgument)) {
$promptParts[] = $baseConversationArgument;
}
// 2. AI Role/Persona
$aiRole = trim($this->componentParams->get('chatgpt_ai_role', ''));
if (!empty($aiRole)) {
$promptParts[] = "Your role: " . $aiRole;
}
// 3. Tone of voice
$aiTone = $this->componentParams->get('chatgpt_ai_tone', 'professional');
$toneMap = array(
'professional' => 'Maintain a professional and competent tone in your responses.',
'friendly' => 'Be friendly and approachable in your responses, using a warm and welcoming tone.',
'casual' => 'Use a casual and relaxed conversational tone.',
'formal' => 'Use a formal and polished tone, appropriate for business communications.',
'empathetic' => 'Be empathetic and understanding, showing care for the user\'s concerns.'
);
if (isset($toneMap[$aiTone])) {
$promptParts[] = $toneMap[$aiTone];
}
// 4. Response language
$aiLanguage = trim($this->componentParams->get('chatgpt_ai_language', ''));
if (!empty($aiLanguage)) {
$promptParts[] = "Always respond in " . $aiLanguage . ".";
}
// 5. Response length
$responseLength = $this->componentParams->get('chatgpt_ai_max_response_length', 'medium');
$lengthMap = array(
'short' => 'Keep your responses concise and brief, ideally 1-3 sentences.',
'medium' => 'Provide moderately detailed responses, typically 2-5 sentences.',
'long' => 'Provide detailed and comprehensive responses when appropriate.'
);
if (isset($lengthMap[$responseLength])) {
$promptParts[] = $lengthMap[$responseLength];
}
// 6. Custom greeting
$aiGreeting = trim($this->componentParams->get('chatgpt_ai_greeting', ''));
if (!empty($aiGreeting)) {
$promptParts[] = "When a user starts a new conversation or greets you, respond with: \"" . $aiGreeting . "\"";
}
// 7. Knowledge Base entries
$knowledgeBase = $this->componentParams->get('chatgpt_knowledge_base', '');
if (!empty($knowledgeBase)) {
if (is_string($knowledgeBase)) {
$knowledgeBase = json_decode($knowledgeBase, true);
}
if (is_object($knowledgeBase)) {
$knowledgeBase = (array)$knowledgeBase;
}
if (is_array($knowledgeBase) && count($knowledgeBase)) {
$kbEntries = array();
foreach ($knowledgeBase as $entry) {
$entry = (array)$entry;
$topic = trim($entry['topic'] ?? '');
$content = trim($entry['content'] ?? '');
if (!empty($topic) && !empty($content)) {
$kbEntries[] = "- " . $topic . ": " . $content;
}
}
if (count($kbEntries)) {
$promptParts[] = "Here is important information you must know and use to answer questions:\n" . implode("\n", $kbEntries);
}
}
}
// 8. Q&A Dictionary
$qaDictionary = $this->componentParams->get('chatgpt_qa_dictionary', '');
if (!empty($qaDictionary)) {
if (is_string($qaDictionary)) {
$qaDictionary = json_decode($qaDictionary, true);
}
if (is_object($qaDictionary)) {
$qaDictionary = (array)$qaDictionary;
}
if (is_array($qaDictionary) && count($qaDictionary)) {
$qaEntries = array();
foreach ($qaDictionary as $qa) {
$qa = (array)$qa;
$question = trim($qa['question'] ?? '');
$answer = trim($qa['answer'] ?? '');
if (!empty($question) && !empty($answer)) {
$qaEntries[] = "Q: " . $question . "\nA: " . $answer;
}
}
if (count($qaEntries)) {
$promptParts[] = "When users ask the following questions or similar, use these reference answers:\n" . implode("\n\n", $qaEntries);
}
}
}
// 9. Forbidden topics
$forbiddenTopics = trim($this->componentParams->get('chatgpt_forbidden_topics', ''));
if (!empty($forbiddenTopics)) {
$promptParts[] = "You must NEVER discuss or provide information about the following topics: " . $forbiddenTopics . ". If asked about these topics, politely decline and redirect the conversation.";
}
// 10. Fallback message
$fallbackMessage = trim($this->componentParams->get('chatgpt_fallback_message', ''));
if (!empty($fallbackMessage)) {
$promptParts[] = "If you cannot answer a question or are unsure, respond with: \"" . $fallbackMessage . "\"";
}
// 11. Include links preference
$includeLinks = $this->componentParams->get('chatgpt_include_links', '1');
if ($includeLinks == '0') {
$promptParts[] = "Do not include URLs or links in your responses.";
}
// 12. Custom additional instructions
$customInstructions = trim($this->componentParams->get('chatgpt_custom_instructions', ''));
if (!empty($customInstructions)) {
$promptParts[] = $customInstructions;
}
return implode("\n\n", $promptParts);
}
/**
* Manage the bot answers to messages through ChatGPT API
*
* @access private
* @return void
*/
private function processChatGPTBotMessages() {
$httpServiceTransport = $this->componentParams->get('aigenerator_service_http_transport', 'curl');
$apiUrl = 'https://api.openai.com/v1/chat/completions';
$chatgptApikey = $this->componentParams->get('chatgpt_apikey', '');
$chatgptApiModel = $this->componentParams->get('chatgpt_api_model', 'gpt-3.5-turbo');
// Check if the model is gpt-4.1 or above
$isNewModel = preg_match('/^gpt-(4\.1|5)/', $chatgptApiModel);
$tokenKeyName = $isNewModel ? 'max_completion_tokens' : 'max_tokens';
$chatgptApiCustomModel = trim($this->componentParams->get('chatgpt_api_custom_model', ''));
if($chatgptApiModel == 'custom' && $chatgptApiCustomModel) {
$chatgptApiModel = $chatgptApiCustomModel;
}
$chatgptMaxTokens = (int)$this->componentParams->get('chatgpt_maxtokens', 3500);
$chatgptMaxMessages = (int)$this->componentParams->get('chatgpt_maxmessages', 25);
$chatgptTemperature = (float)$this->componentParams->get('chatgpt_temperature', 0.5);
$chatgptConversationArgument = $this->componentParams->get('chatgpt_conversation_argument', 'Talk about my site and services https://www.yourdomain.com');
// Build enhanced system prompt from AI instruction system
$systemPrompt = $this->buildAISystemPrompt($chatgptConversationArgument);
$queryParts = array();
$queryParts['SELECT'] = '';
$queryParts['JOIN'] = '';
$queryParts['WHERE'] = '';
// Logic for banned users
if($this->componentParams->get('usersbanning', false)) {
$queryParts['WHERE'] = "\n AND cchat.from NOT IN(" .
"\n SELECT " . $this->dbInstance->quoteName('banned') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat_banned_users') .
"\n WHERE " . $this->dbInstance->quoteName('banning') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id) . ")" .
"\n AND cchat.fromuser NOT IN(" .
"\n SELECT " . $this->dbInstance->quoteName('banned') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat_banned_users_ids') .
"\n WHERE " . $this->dbInstance->quoteName('banning') . " = " . $this->dbInstance->quote($this->myUser->id) . ")";
}
// Multitabs download messages mode
$downloadNewMsgsMode = " AND cchat.read != 1 AND cchat.clientdeleted = 0";
$sql = "SELECT cchat.id, cchat.from, cchat.to, cchat.fromuser, cchat.touser, cchat.actualfrom, cchat.actualto, cchat.message," .
"\n cchat.sent, cchat.read, cchat.type, cchat.status, sess.session_id AS loggedin" . $queryParts['SELECT'] .
"\n FROM #__jchat AS cchat" .
"\n INNER JOIN #__session AS sess ON cchat.from = sess.session_id" .
$queryParts['JOIN'] .
"\n WHERE (cchat.to = ". $this->dbInstance->quote(JChatHelpersUsers::$chatGPTSessionIdentifier) .
"\n AND cchat.from = ". $this->dbInstance->quote($this->userSessionTable->session_id) . $downloadNewMsgsMode . $queryParts['WHERE'] . ")";
$sql .= "\n ORDER BY cchat.id";
$this->dbInstance->setQuery($sql);
$chatGptMessages = $this->dbInstance->loadAssocList();
if(is_array($chatGptMessages) && count($chatGptMessages)) {
// Save private chat message using the private messaging to offline users
$mailer = JChatMailer::getInstance('Joomla');
// Fetch remote data to scrape
$httpTransport = $httpServiceTransport == 'socket' ? 'file_get_contents' : new Curl ();
if (is_object ( $httpTransport )) {
$connectionAdapter = new Http ( $httpTransport, $this->componentParams );
} else {
$connectionAdapter = null;
}
// Init headers
$headers = array (
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $chatgptApikey
);
// If new messages from Chatbot directed to me (this user) has been found go on to answer
if(count($chatGptMessages)) {
// Set all messages as read flagged
foreach ($chatGptMessages as $message) {
// Now update status of all messages received till latest new message as read status
$sql = "UPDATE #__jchat SET `read` = '1' WHERE `id` = " . (int)$message['id'];
$this->dbInstance->setQuery($sql);
$this->dbInstance->execute();
}
// Only make a single request for the last messages if more than one have been found, the conversation from the session already includes them all
$to = $this->userSessionTable->session_id;
$from = JChatHelpersUsers::$chatGPTSessionIdentifier;
$tologged = $message['fromuser'];
$responseChatGPT = null;
// Reload this conversation user input from $_SESSION (conversation history) and always re-post it to ChatGPT for a real conversation
$sessionMessagesConversation = $this->sessionName['jchat_user_' . $from];
$userConversation[] = ['role' => 'system', 'content' => strip_tags($systemPrompt)];
foreach ($sessionMessagesConversation as $thisConversationMessage) {
// Case user message
if($thisConversationMessage['self'] == 1) {
$userConversation[] = ['role' => 'user', 'content' => strip_tags($thisConversationMessage['message'])];
} else {
// Case ChatGPT message
$userConversation[] = ['role' => 'assistant', 'content' => strip_tags($thisConversationMessage['message'])];
}
}
// Keep the number of chat completions tokens below the threshold, reduce the number
if(count($userConversation) > $chatgptMaxMessages) {
$userConversation = array_slice($userConversation, -$chatgptMaxMessages);
}
// Ensure a min max tokens for new models
if($isNewModel && $chatgptMaxTokens < 3500) {
$chatgptMaxTokens = 3500;
}
try {
// CURL lib
if ($connectionAdapter) {
$postData = [
$tokenKeyName => $chatgptMaxTokens,
'model' => $chatgptApiModel,
'n' => 1,
'messages' => $userConversation
];
// Add temperature only if it's an old model
if (!$isNewModel) {
$postData['temperature'] = $chatgptTemperature;
}
$httpResponse = $connectionAdapter->post ( $apiUrl, json_encode($postData), $headers );
if ($httpResponse->code == 200 && $httpResponse->body) {
$responseChatGPT = json_decode($httpResponse->body);
} else {
$responseChatGPT = json_decode($httpResponse->body);
if(isset($responseChatGPT->error->message)) {
$this->storePrivateMessage($to, $tologged, $responseChatGPT->error->message, $mailer, $from);
return;
}
}
} else {
// file_get_contents case
$postData = json_encode ( [
$tokenKeyName => $chatgptMaxTokens,
'model' => $chatgptApiModel,
'n' => 1,
'messages' => $userConversation
] );
// Add temperature only if it's an old model
if (!$isNewModel) {
$postData['temperature'] = $chatgptTemperature;
}
$opts = array (
'http' => array (
'method' => "POST",
'header' => "Content-Type: application/json\r\n" .
"Authorization: Bearer $chatgptApikey\r\n",
'content' => $postData,
'timeout' => 20
)
);
$context = stream_context_create ( $opts );
$httpResponse = file_get_contents ( $apiUrl, false, $context );
if($httpResponse) {
$responseChatGPT = json_decode($httpResponse);
}
}
// ChatGPT answered, go on to parse the response and store the bot message
if($responseChatGPT) {
$choices = $responseChatGPT->choices;
foreach ( $choices as $index => $chatChoice ) {
$responseMessage = $chatChoice->message->content;
$this->storePrivateMessage($to, $tologged, $responseMessage, $mailer, $from);
}
} else {
}
} catch (JChatException $e) {
$this->storePrivateMessage($to, $tologged, $e->getMessage(), $mailer, $from);
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->storePrivateMessage($to, $tologged, $jchatException->getMessage(), $mailer, $from);
}
}
}
}
/**
* Get users status
*
* @access protected
* @return void
*/
protected function getUserStateFromDB() {
// If not guest
if($this->myUser->id) {
$sql = "SELECT " .
$this->dbInstance->quoteName('status') . "," .
$this->dbInstance->quoteName('skypeid') . "," .
$this->dbInstance->quoteName('roomid') .
"\n FROM " .
$this->dbInstance->quoteName('#__jchat_userstatus') .
"\n WHERE " . $this->dbInstance->quoteName('userid') ." = " . $this->dbInstance->quote($this->myUser->id);
$this->dbInstance->setQuery($sql);
$userStatus = $this->dbInstance->loadAssoc();
}
$sql = "SELECT " .
$this->dbInstance->quoteName('status') . "," .
$this->dbInstance->quoteName('override_name') . "," .
$this->dbInstance->quoteName('skypeid') . "," .
$this->dbInstance->quoteName('roomid') .
"\n FROM " .
$this->dbInstance->quoteName('#__jchat_sessionstatus') .
"\n WHERE " . $this->dbInstance->quoteName('sessionid') ." = " . $this->dbInstance->quote($this->userSessionTable->session_id);
$this->dbInstance->setQuery($sql);
$chat = $this->dbInstance->loadAssoc();
if (empty($chat['status'])) {
$chat['status'] = 'available';
} else {
if ($chat['status'] == 'offline') {
$this->sessionName['jchat_sessionvars']['buddylist'] = 0;
}
}
$overrideName = null;
if(!empty($chat['override_name'])) {
$overrideName = $chat['override_name'];
}
$status = null;
if(!empty($userStatus['status'])) {
$status = $userStatus['status'];
} elseif(!empty($chat['status'])) {
$status = $chat['status'];
}
$skypeId = null;
if(!empty($userStatus['skypeid'])) {
$skypeId = $userStatus['skypeid'];
} elseif(!empty($chat['skypeid'])) {
$skypeId = $chat['skypeid'];
}
$roomid = null;
if(!empty($userStatus['roomid'])) {
$roomid = $userStatus['roomid'];
} elseif(!empty($chat['roomid'])) {
$roomid = $chat['roomid'];
} elseif(isset($userStatus['roomid']) || isset($chat['roomid'])) {
$roomid = 0;
}
$status = array('status' => $status, 'override_name' => $overrideName, 'skype_id' => $skypeId, 'room_id' => $roomid);
$this->response['userstatus'] = $status;
}
/**
* Get meeting status
*
* @access protected
* @return string
*/
protected function getMeetingStateFromDB() {
$sql = "SELECT " .
$this->dbInstance->quoteName('meeting_hash') .
"\n FROM " .
$this->dbInstance->quoteName('#__jchat_sessionstatus') .
"\n WHERE " . $this->dbInstance->quoteName('sessionid') ." = " . $this->dbInstance->quote($this->userSessionTable->session_id);
$this->dbInstance->setQuery($sql);
$meetingHash = $this->dbInstance->loadResult();
return $meetingHash;
}
/**
* Check if a user is banned to prevent instantly the chat app execution
*
* @access protected
* @return boolean
*/
protected function getBannedStatus() {
$queryUserBannedUsers = "SELECT" .
$this->dbInstance->quoteName('banstatus') .
"\n FROM #__jchat_userstatus" .
"\n WHERE" .
"\n " . $this->dbInstance->quoteName('userid') . " = " . (int)$this->myUser->id;
$bannedUserStatus = $this->dbInstance->setQuery($queryUserBannedUsers)->loadResult();
$querySessionBannedUsers = "SELECT" .
$this->dbInstance->quoteName('banstatus') .
"\n FROM #__jchat_sessionstatus" .
"\n WHERE" .
"\n " . $this->dbInstance->quoteName('sessionid') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id);
$bannedSessionStatus = $this->dbInstance->setQuery($querySessionBannedUsers)->loadResult();
return (bool)($bannedUserStatus || $bannedSessionStatus);
}
/**
* Inject chatrooms list on first initialize and subsequent buddylist dispatching
*
* @access protected
* @param array $buddylist
* @return void
*/
protected function getChatRoomsList($buddylist) {
// Get list of current chatrooms available
$cachable = $this->componentParams->get('caching', false);
$menuItemid = $this->app->getInput()->getInt('jchat_itemid');
if($cachable) {
// By default callback handler
$cache = $this->getExtensionCache();
$rooms = $cache->get(array($this, 'loadChatRooms'), array($menuItemid));
} else {
$rooms = $this->loadChatRooms($menuItemid);
}
if($buddylist) {
$showChatroomsUsersDetails = $this->componentParams->get('chatrooms_users_details', 1);
$currentlyValidSessions = array_keys($buddylist);
$currentlyValidSessions = "'" . implode("','", $currentlyValidSessions) . "'";
// Count rooms from userstate
$query = "SELECT COUNT(rooms.roomid) AS numusers, rooms.roomid" .
"\n FROM #__jchat_userstatus AS rooms" .
"\n INNER JOIN #__session AS sess" .
"\n ON sess.userid = rooms.userid" .
"\n WHERE " . $this->sessClientId . " AND sess.session_id IN(" . $currentlyValidSessions . ")" .
"\n GROUP BY rooms.roomid";
$this->dbInstance->setQuery($query);
$roomsUserstateCount = $this->dbInstance->loadAssocList('roomid');
// Count rooms from sessionstate
$query = "SELECT COUNT(rooms.roomid) AS numusers, rooms.roomid" .
"\n FROM #__jchat_sessionstatus AS rooms" .
"\n WHERE rooms.sessionid IN(" . $currentlyValidSessions . ")" .
"\n GROUP BY rooms.roomid";
$this->dbInstance->setQuery($query);
$roomsSessionstateCount = $this->dbInstance->loadAssocList('roomid');
// Needs activating users details for joined rooms
// Select all single users that are in a chatroom then merge with room data
if($showChatroomsUsersDetails) {
$query = "SELECT rooms.roomid, sess.session_id AS sessionid" .
"\n FROM #__jchat_userstatus AS rooms" .
"\n INNER JOIN #__session AS sess" .
"\n ON sess.userid = rooms.userid" .
"\n WHERE " . $this->sessClientId . " AND sess.session_id IN(" . $currentlyValidSessions . ")" .
"\n ORDER BY rooms.roomid";
$this->dbInstance->setQuery($query);
$roomsUserstateUsers = $this->dbInstance->loadObjectList();
$query = "SELECT rooms.roomid, rooms.sessionid" .
"\n FROM #__jchat_sessionstatus AS rooms" .
"\n WHERE rooms.sessionid IN(" . $currentlyValidSessions . ")" .
"\n ORDER BY rooms.roomid";
$this->dbInstance->setQuery($query);
$roomsSessionstateUsers = $this->dbInstance->loadObjectList();
}
// Manage total users per each chat rooms
if(count($rooms)) {
foreach ($rooms as &$room) {
if(array_key_exists($room['id'], $roomsUserstateCount)) {
$currentUsers = $roomsUserstateCount[$room['id']]['numusers'];
$room['numusers'] += $currentUsers;
}
if(array_key_exists($room['id'], $roomsSessionstateCount)) {
$currentSessions = $roomsSessionstateCount[$room['id']]['numusers'];
$room['numusers'] += $currentSessions;
}
// Needs activating users details for joined rooms
if($showChatroomsUsersDetails) {
if(count($roomsUserstateUsers)) {
foreach ($roomsUserstateUsers as $userStateUser) {
if($userStateUser->roomid == $room['id']) {
$room['users'][] = $buddylist[$userStateUser->sessionid]['name'];
}
}
}
if(count($roomsSessionstateUsers)) {
foreach ($roomsSessionstateUsers as $sessionStateUser) {
if($sessionStateUser->roomid == $room['id']) {
$room['users'][] = $buddylist[$sessionStateUser->sessionid]['name'];
}
}
}
}
}
}
}
// Inject into client response
$this->response['chatrooms'] = $rooms;
}
/**
* Inject my chatroom users on first initialize and subsequent buddylist dispatching
*
* @access protected
* @access array $buddylist the current buddylist to filter
* @return void
*/
protected function getMyChatRoomUsers($buddylist) {
$usersInMyRoom = array();
if($this->myChatRoom && $buddylist) {
// Session users in my room from valid Joomla sessions JOIN userstate
$query = "SELECT sess.session_id" .
"\n FROM #__session AS sess" .
"\n INNER JOIN #__jchat_sessionstatus AS status" .
"\n ON sess.session_id = status.sessionid" .
"\n WHERE " . $this->sessClientId . " AND status.roomid = " . (int)$this->myChatRoom;
$this->dbInstance->setQuery($query);
$sessionsUserState = $this->dbInstance->loadColumn();
// Session users in my room from valid Joomla sessions JOIN sessionstate
$query = "SELECT sess.session_id" .
"\n FROM #__session AS sess" .
"\n INNER JOIN #__jchat_userstatus AS status" .
"\n ON sess.userid = status.userid" .
"\n WHERE " . $this->sessClientId . " AND status.roomid = " . (int)$this->myChatRoom;
$this->dbInstance->setQuery($query);
$usersUserState = $this->dbInstance->loadColumn();
// Merge both arrays and ensure unique values
$totalSessions = array_merge($sessionsUserState, $usersUserState);
$totalSessions = array_unique($totalSessions);
if(count($totalSessions)) {
foreach ($totalSessions as $userInMyRoom) {
if(array_key_exists($userInMyRoom, $buddylist)) {
$userData = $buddylist[$userInMyRoom];
$usersInMyRoom[] = array('sessionid'=>$userInMyRoom, 'name'=>$userData['name']);
}
}
}
} else {
$usersInMyRoom = false;
}
// Inject into client response
$this->response['users_inmyroom'] = $usersInMyRoom;
}
/**
* Initialize my chat room id if chatroom mode is enabled
*
* @access protected
* @return int chatroom ID if any
*/
protected function getMyChatRoom() {
// Assume user is not joined in any chatroom by default
$this->myChatRoom = false;
// If chatroom mode is enabled, try to check if current user has joined to a chatroom, both as session guest or logged user
if($this->componentParams->get('groupchatmode', 'chatroom') == 'chatroom') {
// Limit received messages to only users that belongs to same my chatroom
$queryMyChatRoom = "SELECT (SELECT userstate.roomid" .
"\n FROM #__jchat_userstatus AS userstate" .
"\n WHERE userstate.userid = " . (int)$this->myUser->id . ") AS user_roomid," .
"\n (SELECT sessionstate.roomid" .
"\n FROM #__jchat_sessionstatus AS sessionstate" .
"\n WHERE sessionstate.sessionid = " . $this->dbInstance->quote($this->userSessionTable->session_id) . ") AS session_roomid";
$myChatRoomInfo = $this->dbInstance->setQuery($queryMyChatRoom)->loadObject();
$this->myChatRoom = $myChatRoomInfo->user_roomid ? $myChatRoomInfo->user_roomid : $myChatRoomInfo->session_roomid;
}
return $this->myChatRoom;
}
/**
* Retrieve and inject into response the last read message ID based on users stream
*
* @access protected
* @return void
*/
protected function getLatestReadMessage() {
$openChatBoxesString = isset($this->sessionName ['jchat_sessionvars']['activeChatboxes']) ? $this->sessionName ['jchat_sessionvars']['activeChatboxes'] : null ;
if($openChatBoxesString) {
// Initialize response array
$this->response['lastreadmessages'] = array();
// Parse the currently opened chatboxes
$chatBoxesIDs = preg_split('/(\|\d+,*)/i', $openChatBoxesString);
array_pop($chatBoxesIDs);
// Cycle and retrieving of the last read message id
foreach ($chatBoxesIDs as $chatBoxID) {
// Select query for the latest read message for the currently opened chatboxes
$query = "SELECT " .
$this->dbInstance->quoteName('id') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat') .
"\n WHERE " . $this->dbInstance->quoteName('from') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id) .
"\n AND " . $this->dbInstance->quoteName('to') . " = " . $this->dbInstance->quote($chatBoxID) .
"\n AND " . $this->dbInstance->quoteName('read') . " = 1" .
"\n ORDER BY " .
$this->dbInstance->quoteName('sent') . " DESC" . "," .
$this->dbInstance->quoteName('id') . " DESC" .
"\n LIMIT 1";
$this->dbInstance->setQuery($query);
$lastMessageID = $this->dbInstance->loadResult();
if($lastMessageID) {
$this->response['lastreadmessages'][$chatBoxID] = $lastMessageID;
}
}
}
}
/**
* Manage users buddylist
*
* @access protected
* @return void
*/
protected function getBuddyList() {
$filter = InputFilter::getInstance();
$buddyList = [];
$userFieldName = $filter->clean($this->componentParams->get('usefullname', 'username'), 'word');
$searchFilter = $this->getState('searchfilter');
$forceRefresh = $this->getState('force_refresh') ? true : false;
//Prendiamo il time per eventuale aggiornamento lista utenti buddylist
$time = time();
// Check if the meetings feature is enabled
$meetingHash = null;
if($this->meetingsFeatureEnabled) {
$meetingHash = $this->getMeetingStateFromDB();
}
// A valid buddylist refresh timeout is detected, so go on
if ((empty($this->sessionName['jchat_buddytime'])) || ($this->requestArray['initialize'] == 1 || ($forceRefresh)) ||
(!empty($this->sessionName['jchat_buddytime']) && ($time-$this->sessionName['jchat_buddytime'] > $this->componentParams->get('chatrefresh', 2) * 2.5))) {
$queryParts = array();
$queryParts['SELECT'] = '';
$queryPartsContacts['SELECT'] = '';
$queryParts['JOIN'] = '';
$accessLevels['JOIN'] = '';
$myUsersGroups['JOIN'] = '';
$queryPartsContacts['JOIN'] = '';
$additionalAND = null;
$accessLevelsAND = null;
$myUsersGroupsAND = null;
$meetingJoinedAND = null;
$livestreamingJoinedAND = null;
$this->response['my_avatar'] = JChatHelpersUsers::getAvatar($this->userSessionTable->session_id);
// LEFT JOIN per group chat status: validcontact = utente � un mio contatto, validowner = utente � owner del mio contatto, stabilisce lo stato/colore delle icone nella buddylist
// Group chat mode management
if($this->componentParams->get('groupchatmode', 'chatroom') == 'invite') {
$queryPartsContacts['SELECT'] = "\n, fbc.contactid AS validcontact, fbch.ownerid AS validowner";
$queryPartsContacts['JOIN'] = "\n LEFT JOIN #__jchat_public_sessionrelations AS fbc ON fbc.contactid = sess.session_id AND fbc.ownerid = " . $this->dbInstance->quote($this->userSessionTable->session_id) .
"\n LEFT JOIN #__jchat_public_sessionrelations AS fbch ON fbch.ownerid = sess.session_id AND fbch.contactid = " . $this->dbInstance->quote($this->userSessionTable->session_id);
}
// Logic for Guest users
$guestMode = $this->componentParams->get('guestenabled', false);
if($guestMode) {
$logicJOIN = 'LEFT';
$joinAND = 'AND u.block = 0';
$logicAND = 'AND ' . $this->sessClientId;
// If form join/meeting ensure to collect users having joined the chat component with a valid form username submission
if($guestMode == 2) {
$logicAND .= ' AND (!ISNULL(ccs.override_name) || sess.userid != 0)';
}
} else {
$logicJOIN = 'INNER';
$joinAND = 'AND u.block = 0';
$logicAND = 'AND sess.guest = 0 AND ' . $this->sessClientId;
}
// Search filter for registered users / override name guests
if($searchFilter && $searchFilter != Text::_('COM_JCHAT_SEARCH')) {
$logicAND .= "\n AND(" .
"\n CASE" .
"\n WHEN (u.$userFieldName != '' OR (ccs.override_name != '' AND ccs.override_name IS NOT NULL))" .
"\n THEN (u.$userFieldName LIKE '%" . $searchFilter . "%'OR ccs.override_name LIKE '%" . $searchFilter . "%')" .
"\n ELSE sess.session_id != ''" .
"\n END)";
}
// Evaluate ONLY FRIENDS 3PD integration option
if($this->componentParams->get('3pdintegration', null) && $this->componentParams->get('filter_friendship', false)) {
$queryPartsFriends = $this->getQueryParts('buddylist');
} else {
$queryPartsFriends['JOIN'] = '';
$queryPartsFriends['WHERE'] = '';
}
// Logic for banned users
if($this->componentParams->get('usersbanning', false)) {
$queryPartsBannedUsers['SELECT'] = "\n, bant.banned, ban.banning, bantids.banned AS bannedid, banids.banning AS banningid";
$queryPartsBannedUsers['JOIN'] = "\n LEFT JOIN #__jchat_banned_users AS bant ON bant.banned = sess.session_id" .
"\n AND bant.banning = " . $this->dbInstance->quote($this->userSessionTable->session_id) . // Users i'm banning to
"\n LEFT JOIN #__jchat_banned_users AS ban ON ban.banning = sess.session_id" .
"\n AND ban.banned = " . $this->dbInstance->quote($this->userSessionTable->session_id) .
"\n LEFT JOIN #__jchat_banned_users_ids AS bantids ON bantids.banned = u.id" .
"\n AND bantids.banning = " . $this->dbInstance->quote($this->myUser->id) . // Users i'm banning to
"\n LEFT JOIN #__jchat_banned_users_ids AS banids ON banids.banning = u.id" .
"\n AND banids.banned = " . $this->dbInstance->quote($this->myUser->id); // Users i'm banned from
} else {
$queryPartsBannedUsers['SELECT'] = '';
$queryPartsBannedUsers['JOIN'] = '';
}
// Manage live support mode filtering
list($queryParts['JOIN'], $additionalAND) = $this->getQueryLiveSupport ();
// Manage chat filtering by access levels
list($accessLevels['JOIN'], $accessLevelsAND) = $this->getQueryAccessLevels();
// Manage chat filtering by same users groups of the current user
if($this->componentParams->get('limit_my_users_groups', 0)) {
list($myUsersGroups['JOIN'], $myUsersGroupsAND) = $this->getQueryMyUsersGroups();
}
// Limit the buddylist to users joining the same meeting
if($this->meetingsFeatureEnabled) {
if($meetingHash) {
// Found a meeting hash for this user? Show only users that are participants in the same meeting
$meetingJoinedAND = "\n AND ccs.meeting_hash = " . $this->dbInstance->quote($meetingHash);
} else {
// Not found a meeting hash for this user? Exclude all users that are participants in a meeting
$meetingJoinedAND = "\n AND ISNULL(ccs.meeting_hash)";
}
}
// Limit the buddylist to users joining the live conference
if($this->livestreamingFeatureEnabled) {
if($liveStreamingHash = $this->app->getInput()->get('livestreaming_identifier')) {
// Found a livestreaming hash? Show only users that are participants in the same live
$livestreamingJoinedAND = "\n AND ccs.livestreaming_hash = " . $this->dbInstance->quote($liveStreamingHash);
} else {
$sql = "UPDATE #__jchat_sessionstatus SET" .
"\n " . $this->dbInstance->quoteName('livestreaming_hash') . " = NULL " .
"\n WHERE " . $this->dbInstance->quoteName('sessionid') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id);
$this->dbInstance->setQuery($sql);
$this->dbInstance->execute();
// Not found a livestreaming hash? Exclude all users that are participants in a live
$livestreamingJoinedAND = "\n AND ISNULL(ccs.livestreaming_hash)";
}
}
$sql = "SELECT u.id, u.$userFieldName, sess.time AS lastactivity, sess.session_id AS loggedin, ccs.status AS session_status, ccs.livestreaming_watch, su.status AS user_status," .
"\n ccs.sessionid AS session_sessid, ccs.roomid AS session_roomid, ccs.override_name, ccs.geoip, su.roomid AS user_roomid," .
"\n CASE WHEN su.skypeid IS NOT NULL THEN su.skypeid ELSE ccs.skypeid END AS skypeid," .
"\n MAX( fb.sent) AS lastmessagetime" .
$queryPartsContacts['SELECT'] .
$queryPartsBannedUsers['SELECT'] .
"\n FROM #__session AS sess" .
"\n $logicJOIN JOIN #__users AS u ON sess.userid = u.id $joinAND".
$queryPartsFriends['JOIN'] .
"\n LEFT JOIN #__jchat_sessionstatus AS ccs ON sess.session_id = ccs.sessionid".
"\n LEFT JOIN #__jchat_userstatus AS su ON u.id = su.userid".
"\n LEFT JOIN #__jchat AS fb ON sess.session_id = fb.from".
$queryParts['JOIN'] .
$accessLevels['JOIN'] .
$myUsersGroups['JOIN'] .
$queryPartsContacts['JOIN'] .
$queryPartsBannedUsers['JOIN'] .
"\n WHERE sess.session_id <> " . $this->dbInstance->quote($this->userSessionTable->session_id) .
" $logicAND " .
$additionalAND .
$accessLevelsAND .
$myUsersGroupsAND .
$meetingJoinedAND .
$livestreamingJoinedAND .
$queryPartsFriends['WHERE'] .
"\n AND ($time - sess.time) < " . (int)$this->componentParams->get('maxinactivitytime', 30) .
"\n AND (ISNULL(su.banstatus) OR su.banstatus = 0)" .
"\n AND (ISNULL(ccs.banstatus) OR ccs.banstatus = 0)" .
"\n GROUP BY sess.session_id" .
"\n ORDER BY u.$userFieldName ASC";
$this->dbInstance->setQuery($sql);
$rows = $this->dbInstance->loadAssocList('loggedin');
// Duplicated helper array
$duplicatedHelperArray = array();
$removeDuplicatedUsers = $this->componentParams->get('remove_duplicated_users', 0);
// If the ChatGPT bot is enabled, manage its visibility based on user group permissions
if($this->componentParams->get('chatgpt_ai_bot_enabled', false)) {
if($this->hasAccessToChatGPTBot()) {
// User has permission: force-add the bot if not already in the results (e.g. filtered out by live support, groups, banning, etc)
if(!array_key_exists(JChatHelpersUsers::$chatGPTSessionIdentifier, $rows)) {
$sql = "SELECT sess.time AS lastactivity, sess.session_id AS loggedin, ccs.status AS session_status," .
"\n ccs.sessionid AS session_sessid, ccs.override_name" .
"\n FROM #__session AS sess" .
"\n LEFT JOIN #__jchat_sessionstatus AS ccs ON sess.session_id = ccs.sessionid".
"\n WHERE sess.session_id = " . $this->dbInstance->quote(JChatHelpersUsers::$chatGPTSessionIdentifier);
$this->dbInstance->setQuery($sql);
$chatGptForcedSession = $this->dbInstance->loadAssoc();
$chatGptForcedSession['id'] = 0;
$chatGptForcedSession['loggedin'] = JChatHelpersUsers::$chatGPTSessionIdentifier;
$chatGptForcedSession['username'] = $this->componentParams->get('chatgpt_ai_bot_name', 'ChatBot');
$chatGptForcedSession['override_name'] = '';
$chatGptForcedSession['session_status'] = null;
$chatGptForcedSession['livestreaming_watch'] = '';
$chatGptForcedSession['user_status'] = '';
$chatGptForcedSession['session_roomid'] = null;
$chatGptForcedSession['geoip'] = '';
$chatGptForcedSession['user_roomid'] = null;
$chatGptForcedSession['skypeid'] = '';
$chatGptForcedSession['lastactivity'] = null;
$chatGptForcedSession['lastmessagetime'] = null;
$chatGptForcedSession['banned'] = '';
$chatGptForcedSession['banning'] = '';
$chatGptForcedSession['bannedid'] = '';
$chatGptForcedSession['banningid'] = '';
$rows[JChatHelpersUsers::$chatGPTSessionIdentifier] = $chatGptForcedSession;
}
} else {
// User does NOT have permission: remove the bot from query results if present (the bot session is returned by the main SQL query via JOIN)
if(array_key_exists(JChatHelpersUsers::$chatGPTSessionIdentifier, $rows)) {
unset($rows[JChatHelpersUsers::$chatGPTSessionIdentifier]);
}
}
}
if(is_array($rows) && count($rows)) {
foreach ($rows as $chat) {
// Bypass skipping users not joined nor as members and nor as form if guest mode is based on form join
if($guestMode == 2 && !$chat['id'] && !$chat['session_sessid']) {continue;}
// LOGIC OVERRIDES dello status utente
$chat['status'] = $chat['user_status'] ? $chat['user_status'] : $chat['session_status'];
if(!is_null($chat['status']) && $chat['status'] == 'offline') {
$chat['status'] = 'offline';
} elseif (!$this->componentParams->get('forceavailable', 0) && (($time-$chat['lastmessagetime']) > $this->componentParams->get('lastmessagetime', 60)) && ($chat['status'] == 'available' || is_null($chat['status'])) && $chat['lastmessagetime']) {
// lo consideriamo offline anche se � inattivo da un periodo di tempo e lo status sarebbe available o neutro
$chat['status'] = 'away|' . $this->convertToDaysHoursMins($time-$chat['lastmessagetime']);
} else {
// Se il forceavailable � on si imposta a available per default se non gi� presente
if(is_null($chat['status'])) {
$chat['status'] = 'available';
}
}
// Get current cycled user avatar
$chat['avatar'] = JChatHelpersUsers::getAvatar($chat['loggedin']);
// Guest name override: user field name -> override name -> auto generated
if(!$chat[$userFieldName]) {
if(!$chat['override_name']) {
$chat[$userFieldName] = JChatHelpersUsers::generateRandomGuestNameSuffix($chat['loggedin'], $this->componentParams);
} else {
$chat[$userFieldName] = $chat['override_name'];
}
}
// Search filter for auto generated guest names
if($searchFilter && $searchFilter != Text::_('COM_JCHAT_SEARCH')) {
if(stripos($chat[$userFieldName], $searchFilter) === false) {
continue;
}
}
// If there is a user id keep track of duplicated records, make sure that only the max lastactivity one is finally included in the buddylist
if($removeDuplicatedUsers && @$chat['id']) {
// Skip themselves users just another instance of the same
if($chat['id'] == $this->myUser->id) {
continue;
}
// Is there a user already included in the duplicatedHelperArray?
if(array_key_exists($chat['id'], $duplicatedHelperArray)) {
$existingLastActivity = $duplicatedHelperArray[$chat['id']]['lastactivity'];
$currentLastActivity = $chat['lastactivity'];
// Compare the existing and current lastactivity and if there is a greater one skip the record
if($currentLastActivity < $existingLastActivity) {
continue;
} elseif($currentLastActivity > $existingLastActivity) {
// If instead there is a greater one unset the existing one from the buddylist
$existingSessionId = $duplicatedHelperArray[$chat['id']]['session_id'];
unset($buddyList[$existingSessionId]);
// Then update the duplicatedHelperArray and go on to add this one to the buddylist
$duplicatedHelperArray[$chat['id']] = array('lastactivity'=>$chat['lastactivity'], 'session_id'=>$chat['loggedin']);
}
} else {
// Just add a record to the duplicatedHelperArray to keep track at next iteration
$duplicatedHelperArray[$chat['id']] = array('lastactivity'=>$chat['lastactivity'], 'session_id'=>$chat['loggedin']);
}
}
$buddyList[$chat['loggedin']] = array('id' => $chat['loggedin'],
'name' => $chat[$userFieldName],
'avatar' => $chat['avatar'],
'status' => $chat['status'],
'time' => $chat['lastactivity'],
'iscontact' => @$chat['validcontact'],
'isowner' => @$chat['validowner'],
'isbanned' => @(bool)($chat['banned'] || $chat['bannedid']),
'imbanned' => @(bool)($chat['banning'] || $chat['banningid']),
'skypeid' => $chat['skypeid'],
'isguest' => @!$chat['id'],
'loggedid' => @$chat['id'],
'iswatcher' => @$chat['livestreaming_watch'],
'hasroomid' => @(bool)($chat['session_roomid'] || $chat['user_roomid']),
'lastmessagetime' => $chat['lastmessagetime'],
'geoip' => @$chat['geoip'],
'profilelink' => $this->getUserProfileLink($chat['id'], $chat[$userFieldName], $this->componentParams)
);
}
}
// Regular case, identical assignment and no additional queries
$publicBuddyList = $buddyList;
// Exclude the public buddylist from the friendship filtering so override the $publicBuddyList
if($this->componentParams->get('3pdintegration', null) && $this->componentParams->get('filter_friendship', false) == 2) {
$publicSql = "SELECT sess.session_id AS loggedin, u.$userFieldName" .
"\n FROM #__session AS sess" .
"\n $logicJOIN JOIN #__users AS u ON sess.userid = u.id $joinAND".
"\n LEFT JOIN #__jchat_sessionstatus AS ccs ON sess.session_id = ccs.sessionid".
"\n LEFT JOIN #__jchat_userstatus AS su ON u.id = su.userid".
$queryParts['JOIN'] .
$accessLevels['JOIN'] .
$myUsersGroups['JOIN'] .
"\n WHERE sess.session_id <> " . $this->dbInstance->quote($this->userSessionTable->session_id) .
" $logicAND " .
$additionalAND .
$accessLevelsAND .
$myUsersGroupsAND .
"\n AND ($time - sess.time) < " . (int)$this->componentParams->get('maxinactivitytime', 30) .
"\n AND (ISNULL(su.banstatus) OR su.banstatus = 0)" .
"\n AND (ISNULL(ccs.banstatus) OR ccs.banstatus = 0)" .
"\n GROUP BY sess.session_id" .
"\n ORDER BY u.$userFieldName ASC";
$this->dbInstance->setQuery($publicSql);
$publicRows = $this->dbInstance->loadAssocList();
if(is_array($publicRows) && count($publicRows)) {
foreach ($publicRows as $publicChat) {
$publicBuddyList[$publicChat['loggedin']] = array(
'id' => $publicChat['loggedin'],
'name' => $publicChat[$userFieldName]
);
}
}
}
//Riaggiorniamo il time in sessione dell'ultimo refresh lista utenti
$this->sessionName['jchat_buddytime'] = $time;
if (!empty($buddyList)) {
$this->response['buddylist'] = $buddyList;
// Iniettiamo anche un array di ID crudo
if(is_array($buddyList) && count($buddyList)) {
foreach ($this->response['buddylist'] as $value) {
$this->response['buddylist_ids'][] = $value['id'];
}
}
} else {
$this->response['buddylist'] = false;
}
// Top scope JS side - Evaluate if user is logged in and has a username from db
if(!$this->myUser->$userFieldName) {
$this->response['my_username'] = JChatHelpersUsers::generateRandomGuestNameSuffix($this->userSessionTable->session_id, $this->componentParams);
} else {
$this->response['my_username'] = $this->myUser->$userFieldName;
}
$this->response['my_email'] = $this->myUser->email;
// Refresh chatrooms list with users number currently joined
$this->getChatRoomsList($publicBuddyList);
// Generate a chatroom users list to show under tooltip
$this->getMyChatRoomUsers($publicBuddyList);
// Refresh latest read message for currently opened chatboxes
if($this->componentParams->get('lastreadmessage', true)) {
$this->getLatestReadMessage();
}
// If messages deletion is enabled go on to retrieve and injetc them to clients
if($this->componentParams->get('messages_deletion', false)) {
$this->fetchDeletedMessages();
}
}
}
/**
* Fetch private messages
*
* @access protected
* @return void
*/
protected function fetchMessages() {
$toOpenChatBoxesArray = array();
$toOpenChatBoxes = null;
$languageTranslatorEnabled = $this->componentParams->get('language_translation_enabled', 0);
$languageTranslatorIncomingMessages = $this->componentParams->get('language_translation_incomingmessages', 0);
$openChatBoxesString = isset($this->sessionName ['jchat_sessionvars']['activeChatboxes']) ? $this->sessionName ['jchat_sessionvars']['activeChatboxes'] : null ;
if($openChatBoxesString) {
$toOpenChatBoxes = array();
$chunks = explode(',', $openChatBoxesString);
foreach ($chunks as $chunk) {
$toOpenChatBoxes[] = @$this->dbInstance->quote(array_shift(explode('|', $chunk)));
}
$toOpenChatBoxesArray = $toOpenChatBoxes;
if($toOpenChatBoxes) {
$toOpenChatBoxes = implode (',', $toOpenChatBoxes);
}
}
$initialize = $this->getState('initialize');
$lastNewMessageID = null;
$lastReceivedMsgID = $this->getState('last_received_msg_id');
$filter = InputFilter::getInstance();
$userFieldName = $filter->clean($this->componentParams->get('usefullname', 'username'), 'word');
$queryParts = array();
$queryParts['SELECT'] = '';
$queryParts['JOIN'] = '';
$queryParts['WHERE'] = '';
// Logic for banned users
if($this->componentParams->get('usersbanning', false)) {
$queryParts['WHERE'] = "\n AND cchat.from NOT IN(" .
"\n SELECT " . $this->dbInstance->quoteName('banned') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat_banned_users') .
"\n WHERE " . $this->dbInstance->quoteName('banning') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id) . ")" .
"\n AND cchat.fromuser NOT IN(" .
"\n SELECT " . $this->dbInstance->quoteName('banned') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat_banned_users_ids') .
"\n WHERE " . $this->dbInstance->quoteName('banning') . " = " . $this->dbInstance->quote($this->myUser->id) . ")";
}
// Multitabs download messages mode
$downloadNewMsgsMode = " AND cchat.read != 1";
if($this->componentParams->get('download_msgs_multitabs_mode', false) && $lastReceivedMsgID > 0) {
$downloadNewMsgsMode = " AND cchat.id > " . (int)$lastReceivedMsgID;
}
$sql = "SELECT cchat.id, cchat.from, cchat.to, cchat.message," .
"\n cchat.sent, cchat.read, cchat.type, cchat.status, u.id AS userid, sess.session_id AS loggedin, u.$userFieldName AS fromuser" . $queryParts['SELECT'] .
"\n FROM #__jchat AS cchat" .
"\n INNER JOIN #__session AS sess ON cchat.from = sess.session_id" .
"\n LEFT JOIN #__users AS u ON sess.userid = u.id" .
$queryParts['JOIN'] .
"\n WHERE (cchat.to = ". $this->dbInstance->quote($this->userSessionTable->session_id) . $downloadNewMsgsMode . $queryParts['WHERE'] . ")";
if (!$initialize && $toOpenChatBoxes && $lastReceivedMsgID > 0) {
$sql .= "\n OR (cchat.from = ". $this->dbInstance->quote($this->userSessionTable->session_id) . " AND cchat.to IN ( " . $toOpenChatBoxes . " ) " .
"\n AND cchat.id > $lastReceivedMsgID AND cchat.type='file' AND cchat.clientdeleted = 0)";
}
$sql .="\n ORDER BY cchat.id";
$this->dbInstance->setQuery($sql);
$rows = $this->dbInstance->loadAssocList();
// Cycle old messages in session and check if avatars still exist or have been deleted in the meanwhile
if($this->componentParams->get('advanced_avatars_mgmt', false)) {
$this->refreshSessionMessagesAvatars($this->messages);
}
// Load the messages sent to the ChatGPT artificial user and answer by querying the API
if($this->componentParams->get('chatgpt_ai_bot_enabled', false) && $this->hasAccessToChatGPTBot()) {
$this->processChatGPTBotMessages();
}
if(is_array($rows) && count($rows)) {
// Language translation of the message
if($languageTranslatorEnabled && $languageTranslatorIncomingMessages) {
// Composer autoloader
require_once JPATH_COMPONENT_ADMINISTRATOR . '/Framework/composer/autoload_real.php';
}
// Add new received messages on stream if any
foreach ($rows as $chatmessage) {
$self = 0;
$old = 0;
if ($chatmessage['from'] == $this->userSessionTable->session_id) {
$chatmessage['from'] = $chatmessage['to'];
$self = 1;
$old = 1;
}
// Translate incoming messages based on current language translator settings for this chatbox user. Discard self messages.
if($languageTranslatorEnabled && $languageTranslatorIncomingMessages && isset($this->sessionName ['jchat_sessionvars']['langvars']->{$chatmessage['from']})) {
// Get post informations and ensure that the language translation request is valid
$sourceLanguage = $this->sessionName ['jchat_sessionvars']['langvars']->{$chatmessage['from']}->sourcelanguage;
$targetLanguage = $this->sessionName ['jchat_sessionvars']['langvars']->{$chatmessage['from']}->targetlanguage;
$langSwitchEnabled = $this->sessionName ['jchat_sessionvars']['langvars']->{$chatmessage['from']}->translatorstatus;
if($langSwitchEnabled && ($sourceLanguage != $targetLanguage)) {
try {
$translatedMessage = \Stichoza\GoogleTranslate\TranslateClient::translate($targetLanguage, $sourceLanguage, $chatmessage['message']);
$chatmessage['message'] = $translatedMessage ? $translatedMessage : $chatmessage['message'];
} catch(\Exception $e) {/*Do nothing, leave the original text message unaltered*/}
}
}
// Get user avatar on the fly for
$chatmessage['avatar'] = JChatHelpersUsers::getAvatar($chatmessage['loggedin']);
// Get profile link
$chatmessage['profilelink'] = $this->getUserProfileLink($chatmessage['userid'], $chatmessage['fromuser'], $this->componentParams);
// Guest name override: user field name -> override name -> auto generated
if(!$chatmessage['fromuser']) {
$chatmessage['fromuser'] = JChatHelpersUsers::generateRandomGuestNameSuffix($chatmessage['loggedin'], $this->componentParams);
}
$messageUserTime = HTMLHelper::_('date', $chatmessage['sent'], Text::_('DATE_FORMAT_LC2'));
$this->messages[] = array( 'id' => $chatmessage['id'],
'from' => $chatmessage['from'],
'fromuser' => @$chatmessage['fromuser'],
'avatar' => $chatmessage['avatar'],
'profilelink' => @$chatmessage['profilelink'],
'message' => stripslashes($chatmessage['message']),
'type' => @$chatmessage['type'],
'status' => @$chatmessage['status'],
'time' => $messageUserTime,
'self' => $self,
'old' => $old,
'idregistered' => $chatmessage['userid']);
// Store new streamed messages into session if not own messages, old messages and already read
if ($self == 0 && $old == 0 && $chatmessage['read'] != 1) {
$this->sessionName['jchat_user_'.$chatmessage['from']][$chatmessage['id']] = array('id' => $chatmessage['id'],
'from' => $chatmessage['from'],
'fromuser' => @$chatmessage['fromuser'],
'avatar' => $chatmessage['avatar'],
'userid' => @$chatmessage['loggedin'],
'profilelink' => @$chatmessage['profilelink'],
'message' => stripslashes($chatmessage['message']),
'type' => @$chatmessage['type'],
'status' => @$chatmessage['status'],
'time' => $messageUserTime,
'self' => 0,
'old' => 1);
}
$lastNewMessageID = $chatmessage['id'];
}
}
// Now update status of all messages received till latest new message as read status
if ($lastNewMessageID) {
$sql = "UPDATE #__jchat SET `read` = '1' WHERE `to` = " .
$this->dbInstance->quote($this->userSessionTable->session_id) . " and `id` <= " . $this->dbInstance->quote($lastNewMessageID);
$this->dbInstance->setQuery($sql);
$this->dbInstance->execute();
}
// Recover old messages from history for opened chatboxes
if(($historyAutoloadValue = $this->componentParams->get('history_autoload', null)) && !empty($toOpenChatBoxesArray) && $this->myUser->id) {
foreach ($toOpenChatBoxesArray as $chatBoxOpened) {
$chatBoxOpened = trim($chatBoxOpened, "'");
// Only recover old messages if there is nothing from this user in the current session
if(!isset($this->sessionName['jchat_user_'.$chatBoxOpened])) {
$sessionSql = "SELECT" .
"\n " . $this->dbInstance->quoteName('userid') .
"\n FROM #__session" .
"\n WHERE" .
"\n " . $this->dbInstance->quoteName('session_id') . " = " . $this->dbInstance->quote($chatBoxOpened) .
"\n ORDER BY " . $this->dbInstance->quoteName('time') . " DESC" .
"\n LIMIT 1";
$this->dbInstance->setQuery($sessionSql);
$userLoggedId = $this->dbInstance->loadResult();
if($userLoggedId) {
$this->fetchHistoryMessages($userLoggedId, $chatBoxOpened, $historyAutoloadValue, null);
}
}
}
}
// Do autorefresh realtime for messages type=file status
$this->refreshMsgFileSessionStatus($this->response);
}
/**
* Fetch public group messages
*
* @access protected
* @return void
*/
protected function fetchWallMessages() {
$filter = InputFilter::getInstance();
$userFieldName = $filter->clean($this->componentParams->get('usefullname', 'username'), 'word');
// Check if the meetings feature is enabled
$meetingHash = null;
if($this->meetingsFeatureEnabled) {
$meetingHash = $this->getMeetingStateFromDB();
}
$wallHistory = $this->getState('wallhistory', false);
$chatRoomDelay = 0;
$queryParts = array();
$logicAND = null;
$myUsersGroupsAND = null;
$queryPartsMeeting['JOIN'] = '';
$queryPartsMeeting['WHERE'] = '';
$queryParts['SELECT'] = '';
$queryParts['JOIN'] = '';
$myUsersGroups['JOIN'] = '';
$queryParts['WHERE'] = '';
$joinSession = 'INNER';
$excludeMyMessageAND = " AND cchat.from != " . $this->dbInstance->quote($this->userSessionTable->session_id);
$excludeReadMessageAND = "\n AND cchat.id NOT IN (SELECT messageid FROM #__jchat_public_readmessages WHERE sessionid = " . $this->dbInstance->quote($this->userSessionTable->session_id) . ")";
// Check additional timing for just joined to chatroom users
if(isset($this->sessionName['jchat_justjoined']) && $this->componentParams->get('chatrooms_latest', 1)) {
$chatRoomDelay = $this->componentParams->get('chatrooms_latest_interval', 120);
unset($this->sessionName['jchat_justjoined']);
}
// Add special chat room delay if wall history is requested
if($wallHistory) {
$chatRoomDelay = $this->componentParams->get('wall_history_delay', 1) * 60 * 60 * 24;
$joinSession = 'LEFT';
$excludeMyMessageAND = null;
$excludeReadMessageAND = null;
}
// Multitabs download messages mode
if($this->componentParams->get('download_msgs_multitabs_mode', false)) {
$excludeReadMessageAND = null;
}
// Reserved to chatroom filtering
$queryParts['CHATROOM_AND'] = '';
// Group chat mode management
if($this->componentParams->get('groupchatmode', 'chatroom') == 'invite') {
$logicAND = " AND cchat.from IN (SELECT contactid FROM #__jchat_public_sessionrelations" .
"\n WHERE ownerid = " . $this->dbInstance->quote($this->userSessionTable->session_id) . ")" .
" AND cchat.from IN (SELECT ownerid FROM #__jchat_public_sessionrelations" .
"\n WHERE contactid = " . $this->dbInstance->quote($this->userSessionTable->session_id) . ")";
} elseif($this->componentParams->get('groupchatmode', 'chatroom') == 'chatroom') {
// If detected a valid user chatroom that belongs to, proceed with filtering of incoming messages from other users in same chatroom
if($this->myChatRoom) {
$queryParts['CHATROOM_AND'] = "\n AND cchat.sentroomid = " . (int)$this->myChatRoom;
// Filter also by current users in this chatroom, let download chatroom messages only from user still in that chatroom
if($this->componentParams->get('chatrooms_messages_stillinroom', 0)) {
$queryParts['CHATROOM_AND'] .= "\n AND (cchat.from IN(SELECT DISTINCT sessionstate.sessionid" .
"\n FROM #__jchat_sessionstatus AS sessionstate" .
"\n WHERE sessionstate.roomid = " . (int)$this->myChatRoom . ")" .
"\n OR cchat.from IN(SELECT DISTINCT session.session_id" .
"\n FROM #__session AS session" .
"\n INNER JOIN #__jchat_userstatus AS userstate" .
"\n ON session.userid = userstate.userid" .
"\n WHERE " . $this->sessionClientId . " AND userstate.roomid = " . (int)$this->myChatRoom . "))";
}
} else {
// If user doesn't belong to any chatroom, proceed with filtering of incoming messages from other users in no one chatrooms
$queryParts['CHATROOM_AND'] = "\n AND (cchat.sentroomid = 0 OR cchat.sentroomid IS NULL)";
// Filter also by current users in no chatroom, let download messages only from user still in no chatrooms
if($this->componentParams->get('chatrooms_messages_stillinroom', 0)) {
$queryParts['CHATROOM_AND'] .= "\n AND (cchat.from NOT IN(SELECT DISTINCT sessionstate.sessionid" .
"\n FROM #__jchat_sessionstatus AS sessionstate" .
"\n WHERE sessionstate.roomid > 0)" .
"\n AND cchat.from NOT IN(SELECT DISTINCT session.session_id" .
"\n FROM #__session AS session" .
"\n INNER JOIN #__jchat_userstatus AS userstate" .
"\n ON session.userid = userstate.userid" .
"\n WHERE " . $this->sessionClientId . " AND userstate.roomid > 0))";
}
}
// Limit stream of messages to live support admin if active
if($this->componentParams->get('affect_public_chat', 1)) {
list($queryParts['JOIN'], $logicAND) = $this->getQueryLiveSupport ();
}
// Manage chat filtering by same users groups of the current user
if($this->componentParams->get('limit_my_users_groups', 0)) {
list($myUsersGroups['JOIN'], $myUsersGroupsAND) = $this->getQueryMyUsersGroups();
}
} else {
// Limit stream of messages to live support admin if active
if($this->componentParams->get('affect_public_chat', 1)) {
list($queryParts['JOIN'], $logicAND) = $this->getQueryLiveSupport ();
}
// Manage chat filtering by same users groups of the current user
if($this->componentParams->get('limit_my_users_groups', 0)) {
list($myUsersGroups['JOIN'], $myUsersGroupsAND) = $this->getQueryMyUsersGroups();
}
}
// Limit the buddylist to users joining the same meeting
if($this->meetingsFeatureEnabled) {
if($meetingHash) {
// Found a meeting hash for this user? Get only public messages from users that are participants in the same meeting
$queryPartsMeeting['JOIN'] = "\n LEFT JOIN #__jchat_sessionstatus AS ccs ON cchat.from = ccs.sessionid";
$queryPartsMeeting['WHERE'] = "\n AND ccs.meeting_hash = " . $this->dbInstance->quote($meetingHash);
} else {
// Not found a meeting hash for this user? Exclude all messages from users that are participants in a meeting
$queryPartsMeeting['JOIN'] = "\n LEFT JOIN #__jchat_sessionstatus AS ccs ON cchat.from = ccs.sessionid";
$queryPartsMeeting['WHERE'] = "\n AND ISNULL(ccs.meeting_hash)";
}
}
// Evaluate ONLY FRIENDS 3PD integration option
if($this->componentParams->get('3pdintegration', null) && $this->componentParams->get('filter_friendship', false) == 1) {
$queryPartFriends = $this->getQueryParts('groupmessages');
$logicAND .= $queryPartFriends['AND'];
}
// Logic for banned users
if($this->componentParams->get('usersbanning', false) && $this->componentParams->get('usersbanning_mode', 'private') == 'private_public') {
$queryParts['WHERE'] = "\n AND cchat.from NOT IN(" .
"\n SELECT " . $this->dbInstance->quoteName('banned') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat_banned_users') .
"\n WHERE " . $this->dbInstance->quoteName('banning') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id) . ")" .
"\n AND cchat.fromuser NOT IN(" .
"\n SELECT " . $this->dbInstance->quoteName('banned') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat_banned_users_ids') .
"\n WHERE " . $this->dbInstance->quoteName('banning') . " = " . $this->dbInstance->quote($this->myUser->id) . ")";
}
$sql = "SELECT DISTINCT cchat.id, cchat.from, cchat.from AS loggedin, cchat.to, cchat.message," .
"\n cchat.sent, cchat.read, u.id AS userid, u.$userFieldName AS fromuser, cchat.actualfrom" . $queryParts['SELECT'] .
"\n FROM #__jchat AS cchat" .
"\n " . $joinSession . " JOIN #__session AS sess ON cchat.from = sess.session_id" .
"\n LEFT JOIN #__users AS u ON sess.userid = u.id" .
$queryParts['JOIN'] .
$myUsersGroups['JOIN'] .
$queryPartsMeeting['JOIN'] .
"\n WHERE cchat.to = " . $this->dbInstance->quote(0) .
$excludeMyMessageAND .
$queryPartsMeeting['WHERE'] .
$logicAND .
$myUsersGroupsAND .
$queryParts['CHATROOM_AND'] .
"\n AND cchat.sent > " . (time() - $this->componentParams->get('maxtimeinterval_groupmessages', 12) - $chatRoomDelay) .
$excludeReadMessageAND .
$queryParts['WHERE'] .
"\n ORDER BY cchat.id";
$this->dbInstance->setQuery($sql);
$rows = $this->dbInstance->loadAssocList();
// Cycle old messages in session and check if avatars still exist or have been deleted in the meanwhile
if($this->componentParams->get('advanced_avatars_mgmt', false)) {
$this->refreshSessionMessagesAvatars($this->wallMessages, 'wall');
}
if(is_array($rows) && count($rows)) {
// Add new received messages on stream if any
foreach ($rows as $chatmessage) {
$self = 0;
$old = 0;
if ($chatmessage['from'] == $this->userSessionTable->session_id) {
$self = 1;
}
// Get user avatar
$chatmessage['avatar'] = JChatHelpersUsers::getAvatar($chatmessage['loggedin']);
// Get profile link
$chatmessage['profilelink'] = $this->getUserProfileLink($chatmessage['userid'], $chatmessage['fromuser'], $this->componentParams);
// Guest name override: user field name -> override name -> auto generated
if(!$chatmessage['fromuser']) {
if($chatmessage['actualfrom']) {
$chatmessage['fromuser'] = $chatmessage['actualfrom'];
} else {
$chatmessage['fromuser'] = JChatHelpersUsers::generateRandomGuestNameSuffix($chatmessage['loggedin'], $this->componentParams);
}
}
$messageUserTime = HTMLHelper::_('date', $chatmessage['sent'], Text::_('DATE_FORMAT_LC2'));
$this->wallMessages[] = array( 'id' => $chatmessage['id'],
'from' => 'wall',
'fromuserid' => @$chatmessage['from'],
'fromuser' => @$chatmessage['fromuser'],
'avatar' => @$chatmessage['avatar'],
'profilelink' => @$chatmessage['profilelink'],
'message' => stripslashes($chatmessage['message']),
'time' => $messageUserTime,
'self' => $self,
'old' => $old);
$this->sessionName['jchat_user_wall'][$chatmessage['id']] = array (
'id' => $chatmessage['id'],
'from' => 'wall',
'fromuserid' => @$chatmessage['from'],
'fromuser' => @$chatmessage['fromuser'],
'avatar' => @$chatmessage['avatar'],
'userid' => @$chatmessage['loggedin'],
'profilelink' => @$chatmessage['profilelink'],
'message' => stripslashes($chatmessage['message']),
'time' => $messageUserTime,
'self' => $self,
'old' => 1);
// Store new public streamed messages into session
$sql = "INSERT IGNORE INTO #__jchat_public_readmessages VALUES(" . (int)$chatmessage['id'] . "," . $this->dbInstance->quote($this->userSessionTable->session_id) . ")";
$this->dbInstance->setQuery($sql);
$this->dbInstance->execute();
}
}
}
/**
* Get typing status for chatbox users that are writing to this me user
*
* @access protected
* @return void
*/
protected function fetchTypingStatus() {
// Load all typing users to my session id, currently active, if none save bandwidth and avoid response
$query = "SELECT " .
$this->dbInstance->quoteName('sessionid') . "," .
$this->dbInstance->quoteName('typing_to') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat_sessionstatus') .
"\n WHERE" .
"\n " . $this->dbInstance->quoteName('typing') . " = 1" .
"\n AND " . $this->dbInstance->quoteName('typing_to') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id);
$this->dbInstance->setQuery($query);
$results = $this->dbInstance->loadAssocList('sessionid');
$this->response['typing_status'] = $results;
}
/**
* Get deleted messages to this me user or to the generic wall group chat
*
* @access protected
* @return void
*/
protected function fetchDeletedMessages() {
// Load all deleted messages that are sent to me this session user or to the generic wall and multiple users
$query = "SELECT " .
$this->dbInstance->quoteName('messageid') . "," .
$this->dbInstance->quoteName('from') . "," .
$this->dbInstance->quoteName('to') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat_deleted_messages') .
"\n WHERE" .
"\n (" . $this->dbInstance->quoteName('to') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id) .
"\n OR " . $this->dbInstance->quoteName('to') . " = 'wall')" ;
$this->dbInstance->setQuery($query);
$results = $this->dbInstance->loadAssocList('messageid');
// The total amount of messages are injected in the JS client domain
$this->response['deleted_messages'] = $results;
// Go on to delete private messages from the current SESSION for this user
if(count($results)) {
$resultIds = array(-1); // This -1 is done if there are no private messages but only public wall messages
foreach ($results as $messageID=>$conversation) {
if (isset($this->sessionName['jchat_user_'.$conversation['from']])) {
// Delete the private user message from the session
if(is_array($this->sessionName['jchat_user_'.$conversation['from']]) && isset($this->sessionName['jchat_user_'.$conversation['from']][$messageID])) {
// Unset this message from the session array
unset($this->sessionName['jchat_user_'.$conversation['from']][$messageID]);
$resultIds[] = $messageID;
}
}
if ($conversation['to'] == 'wall') {
// Delete the private user message from the session
if(is_array($this->sessionName['jchat_user_wall']) && isset($this->sessionName['jchat_user_wall'][$messageID])) {
// Unset this message from the session array
unset($this->sessionName['jchat_user_wall'][$messageID]);
}
}
}
// Delete past messages sent to single users that is not wall, the wall is deleted by expiring time
$time = time();
$query = "DELETE FROM #__jchat_deleted_messages" .
"\n WHERE (" .
$this->dbInstance->quoteName('messageid') . " IN (" . implode(',', $resultIds) . ")" .
"\n OR (" .
"\n ($time - sent) > " . (int)$this->componentParams->get('maxinactivitytime', 30) .
"\n AND " . $this->dbInstance->quoteName('from') . " != " . $this->dbInstance->quote($this->userSessionTable->session_id) .
"\n AND " . $this->dbInstance->quoteName('to') . "= 'wall'))";
$this->dbInstance->setQuery($query);
$this->dbInstance->execute();
}
}
/**
* Listening for WebRTC signaling channel and incoming
* video calls from remote peer
* This signaling channel response is bypassed when peer is a caller
* that has received an answer, or a callee that has received an offer
* The signaling channel query is ALWAYS executed to monitor call changes
*
* @access protected
* @return void
*/
protected function fetchSignalingChannel() {
// Listen for signaling channel, listen for both SDP messages or ICE candidate
$query = "SELECT *" .
"\n FROM #__jchat_webrtc" .
"\n WHERE " . $this->dbInstance->quoteName('peer2') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id);
$dataObject = $this->dbInstance->setQuery($query)->loadObject();
// Check if the peer1 is a valida caller. The callee could have close the call in the meanwhile
$callerQuery = "SELECT " . $this->dbInstance->quoteName('peer1') .
"\n FROM #__jchat_webrtc" .
"\n WHERE " . $this->dbInstance->quoteName('peer1') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id);
$checkCallerPeerState = $this->dbInstance->setQuery($callerQuery)->loadResult();
// Security safe: are both data for ICE frameword and sdp correctly available? Otherwise posticipate
if(!is_object($dataObject) || !$dataObject->sdp || !$dataObject->icecandidate || $dataObject->icecandidate == '[]') {
$this->response['webrtc_signaling_channel']['call_status'] = 0;
$this->response['webrtc_signaling_channel']['caller_peer_state'] = $checkCallerPeerState;
// No peer data found this means that the call is not valid or ended
if(isset($this->sessionName['jchat_webrtc_datareceived'])) {
unset($this->sessionName['jchat_webrtc_datareceived']);
}
return;
}
// Are we dealing with a valid incoming call or a peer response?
// This is the first select query, so answer with full SDP and ICE data
if(!isset($this->sessionName['jchat_webrtc_datareceived'])) {
$dataObject->call_status = 1;
$dataObject->caller_peer_state = $checkCallerPeerState;
$this->response['webrtc_signaling_channel'] = $dataObject;
// Store session for this peer as data received, this will stop full response data sending only call status
$this->sessionName['jchat_webrtc_datareceived'] = true;
} else {
$this->response['webrtc_signaling_channel']['call_status'] = 1;
$this->response['webrtc_signaling_channel']['caller_peer_state'] = $checkCallerPeerState;
$this->response['webrtc_signaling_channel']['videocam'] = $dataObject->videocam;
}
}
/**
* Listening for WebRTC signaling channel and incoming
* video calls from remote peer during a conference and multi users
* This signaling channel response is bypassed when peer is a caller
* that has received an answer, or a callee that has received an offer
* The signaling channel query is ALWAYS executed to monitor call changes
*
* @access protected
* @return void
*/
protected function fetchConferenceSignalingChannel() {
// Listen for signaling channel, listen for both SDP messages or ICE candidate
$query = "SELECT *" .
"\n FROM #__jchat_webrtc_conference" .
"\n WHERE " . $this->dbInstance->quoteName('peer2') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id);
$dataObjects = $this->dbInstance->setQuery($query)->loadObjectList();
// Check if valid channels have been retrieved
if(!empty($dataObjects)) {
foreach ($dataObjects as $numSessionIndex=>$dataObject) {
// Check if the peer1 is a valid caller. The callee could have close the call in the meanwhile
$callerQuery = "SELECT " . $this->dbInstance->quoteName('peer1') .
"\n FROM #__jchat_webrtc_conference" .
"\n WHERE " . $this->dbInstance->quoteName('peer1') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id) .
"\n AND " . $this->dbInstance->quoteName('peer2') . " = " . $this->dbInstance->quote($dataObject->peer1);
$checkCallerPeerState = $this->dbInstance->setQuery($callerQuery)->loadResult();
// Estabilish a session exchange hash as unique identifier
$sessionHash = md5($dataObject->peer1 . $dataObject->peer2);
// Security safe: are both data for ICE frameword and sdp correctly available? Otherwise posticipate
if(!is_object($dataObject) || !$dataObject->sdp || !$dataObject->icecandidate || $dataObject->icecandidate == '[]') {
$dataObject->call_status = 0;
$dataObject->caller_peer_state = $checkCallerPeerState;
$this->response['webrtc_conference_signaling_channel'][] = $dataObject;
// No peer data found this means that the call is not valid or ended
if(isset($this->sessionName['jchat_conference_webrtc_datareceived'][$sessionHash])) {
unset($this->sessionName['jchat_conference_webrtc_datareceived'][$sessionHash]);
}
return;
}
// Are we dealing with a valid incoming call or a peer response?
// This is the first select query, so answer with full SDP and ICE data
if(!isset($this->sessionName['jchat_conference_webrtc_datareceived'][$sessionHash])) {
$dataObject->call_status = 1;
$dataObject->caller_peer_state = $checkCallerPeerState;
$this->response['webrtc_conference_signaling_channel'][] = $dataObject;
// Store session for this peer as data received, this will stop full response data sending only call status
$this->sessionName['jchat_conference_webrtc_datareceived'][$sessionHash] = true;
} else {
$dataObject->call_status = 1;
$dataObject->caller_peer_state = $checkCallerPeerState;
// Avoid doubling full start sessions
unset($dataObject->sdp);
unset($dataObject->icecandidate);
unset($dataObject->other_peers);
$this->response['webrtc_conference_signaling_channel'][] = $dataObject;
}
// Swap $dataObjects index from numeric to hash based
$dataObjects[$sessionHash] = $dataObject;
unset($dataObjects[$numSessionIndex]);
}
}
// Store, subtract and reset previous connection sessions no more available, find previous stale sessions
$staleSessions = array();
if(isset($this->sessionName['jchat_conference_webrtc_sessions']) && count($this->sessionName['jchat_conference_webrtc_sessions'])) {
foreach ($this->sessionName['jchat_conference_webrtc_sessions'] as $staleSessionHash=>$staleSession) {
if(!array_key_exists($staleSessionHash, $dataObjects)) {
$staleSessions[] = $staleSession;
}
}
if(!empty($staleSessions)) {
foreach($staleSessions as $numSessionIndex=>$sessionStaleDataObject) {
$sessionStaleDataObject->call_status = 0;
$sessionStaleDataObject->caller_peer_state = 0;
// Avoid doubling full start sessions
unset($sessionStaleDataObject->sdp);
unset($sessionStaleDataObject->icecandidate);
unset($sessionStaleDataObject->other_peers);
$this->response['webrtc_conference_signaling_channel'][] = $sessionStaleDataObject;
// No peer data found this means that the call is not valid or ended
$sessionHash = md5($sessionStaleDataObject->peer1 . $sessionStaleDataObject->peer2);
if(isset($this->sessionName['jchat_conference_webrtc_datareceived'][$sessionHash])) {
unset($this->sessionName['jchat_conference_webrtc_datareceived'][$sessionHash]);
}
}
}
}
// Regular assignments refresh for the next execution
$this->sessionName['jchat_conference_webrtc_sessions'] = $dataObjects;
// Listen for declined calls by the callee, caller is no more valid caller_peer_state
$query = "SELECT *" .
"\n FROM #__jchat_webrtc_conference" .
"\n WHERE " . $this->dbInstance->quoteName('peer1') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id);
$dataCallerObjects = $this->dbInstance->setQuery($query)->loadObjectList('peer2');
$staleCallerSessions = array();
// Store, subtract and reset previous connection sessions no more available
if(isset($this->sessionName['jchat_conference_webrtc_caller_sessions']) && count($this->sessionName['jchat_conference_webrtc_caller_sessions'])) {
foreach ($this->sessionName['jchat_conference_webrtc_caller_sessions'] as $callerSession) {
if(!array_key_exists($callerSession->peer2, $dataCallerObjects)) {
$staleCallerSessions[] = $callerSession;
}
}
if(!empty($staleCallerSessions)) {
foreach($staleCallerSessions as $numSessionIndex=>$sessionCallerStaleDataObject) {
$sessionCallerStaleDataObject->call_status = 0;
$sessionCallerStaleDataObject->caller_peer_state = 0;
$sessionCallerStaleDataObject->videocam = 0;
$peer1Caller = $sessionCallerStaleDataObject->peer1;
$peer2Callee = $sessionCallerStaleDataObject->peer2;
// Inversion: the peer1 must become the callee peer2 from the caller perspective in the JS client app
$sessionCallerStaleDataObject->peer1 = $peer2Callee;
$sessionCallerStaleDataObject->peer2 = $peer1Caller;
// Avoid doubling full start sessions
unset($sessionCallerStaleDataObject->sdp);
unset($sessionCallerStaleDataObject->icecandidate);
unset($sessionCallerStaleDataObject->other_peers);
$this->response['webrtc_conference_signaling_channel'][] = $sessionCallerStaleDataObject;
}
}
}
// Regular assignments for the next execution
$this->sessionName['jchat_conference_webrtc_caller_sessions'] = $dataCallerObjects;
}
/**
* Listening for WebRTC signaling channel and incoming balckboard sharing sessions
* This signaling channel response is bypassed when peer is a caller
* that has received an answer, or a callee that has received an offer
* The signaling channel query is ALWAYS executed to monitor sharing session changes
*
* @access protected
* @return void
*/
protected function fetchBlackboardSignalingChannel() {
// Listen for signaling channel, listen for both SDP messages or ICE candidate
$query = "SELECT *" .
"\n FROM #__jchat_webrtc_blackboard" .
"\n WHERE " . $this->dbInstance->quoteName('peer2') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id);
$dataObject = $this->dbInstance->setQuery($query)->loadObject();
// Check if the peer1 is a valida caller. The callee could have close the call in the meanwhile
$callerQuery = "SELECT " . $this->dbInstance->quoteName('peer1') .
"\n FROM #__jchat_webrtc_blackboard" .
"\n WHERE " . $this->dbInstance->quoteName('peer1') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id);
$checkCallerPeerState = $this->dbInstance->setQuery($callerQuery)->loadResult();
// Security safe: are both data for ICE frameword and sdp correctly available? Otherwise posticipate
if(!is_object($dataObject) || !$dataObject->sdp || !$dataObject->icecandidate || $dataObject->icecandidate == '[]') {
$this->response['webrtc_blackboard_signaling_channel']['call_status'] = 0;
$this->response['webrtc_blackboard_signaling_channel']['caller_peer_state'] = $checkCallerPeerState;
// No peer data found this means that the call is not valid or ended
if(isset($this->sessionName['jchat_webrtc_blackboard_datareceived'])) {
unset($this->sessionName['jchat_webrtc_blackboard_datareceived']);
}
return;
}
// Are we dealing with a valid incoming call or a peer response?
// This is the first select query, so answer with full SDP and ICE data
if(!isset($this->sessionName['jchat_webrtc_blackboard_datareceived'])) {
$dataObject->call_status = 1;
$dataObject->caller_peer_state = $checkCallerPeerState;
$this->response['webrtc_blackboard_signaling_channel'] = $dataObject;
// Store session for this peer as data received, this will stop full response data sending only call status
$this->sessionName['jchat_webrtc_blackboard_datareceived'] = true;
} else {
$this->response['webrtc_blackboard_signaling_channel']['call_status'] = 1;
$this->response['webrtc_blackboard_signaling_channel']['caller_peer_state'] = $checkCallerPeerState;
}
}
/**
* Ensure to flush the signaling channel if user refresh browser and
* start a new page load.
* Old sessions messages have to be reset and cleared
*
* @access protected
* @return void
*/
protected function clearSignalingChannel() {
// Normal channel clearing
$query = "DELETE FROM #__jchat_webrtc" .
"\n WHERE " . $this->dbInstance->quoteName('peer1') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id) .
"\n OR " . $this->dbInstance->quoteName('peer2') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id);
$this->dbInstance->setQuery($query)->execute();
// Conference channel clearing
$query = "DELETE FROM #__jchat_webrtc_conference" .
"\n WHERE " . $this->dbInstance->quoteName('peer1') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id) .
"\n OR " . $this->dbInstance->quoteName('peer2') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id);
$this->dbInstance->setQuery($query)->execute();
// Blackboard channel clearing
$query = "DELETE FROM #__jchat_webrtc_blackboard" .
"\n WHERE " . $this->dbInstance->quoteName('peer1') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id) .
"\n OR " . $this->dbInstance->quoteName('peer2') . " = " .
$this->dbInstance->quote($this->userSessionTable->session_id);
$this->dbInstance->setQuery($query)->execute();
// Clear session statues
unset($this->sessionName['jchat_conference_webrtc_datareceived']);
unset($this->sessionName['jchat_conference_webrtc_sessions']);
unset($this->sessionName['jchat_conference_webrtc_caller_sessions']);
}
/**
* Refresh session messages avatars, based on avatar changes managed by frontend users,
* both avatar changes or avatar delete
*
* @access protected
* @param array& $messages
* @param string $messageType Switch to evaluate single user or wall messages in session
* @return void
*/
protected function refreshSessionMessagesAvatars(&$messages, $messageType = 'user') {
$avatarCache = array();
$destination = null;
if(is_array($messages) && count($messages)) {
// Cycle on all messages
foreach ($messages as $msgIndex=>&$sessionMessage) {
// Forcing e smistamento in base al message type
if($messageType == 'wall') {
// Messagetype uguale a private user
if($sessionMessage['from'] != 'wall') {
continue;
}
$destination = 'wall';
} else {
// Messagetype uguale a private user
if($sessionMessage['from'] == 'wall') {
continue;
}
if(isset($sessionMessage['userid'])) {
$destination = $sessionMessage['userid'];
}
}
// Ignoriamo self message o messaggi senza to
if(!is_null($destination) && $sessionMessage['self'] != 1) {
// Controllo esistenza immagine SOLO SE c'� un'immagine avatar
if(isset($sessionMessage['avatar'])) {
if(!isset($avatarCache[$sessionMessage['userid']])) {
if(!@file_get_contents($sessionMessage['avatar'])) {
$sessionMessage['avatar'] = JChatHelpersUsers::getAvatar($sessionMessage['userid']);
// Update $this->sessionName
$this->sessionName ['jchat_user_' . $destination][$msgIndex]['avatar'] = $sessionMessage['avatar'];
}
// Cache storing
$avatarCache[$sessionMessage['userid']] = $sessionMessage['avatar'];
} else {
if($sessionMessage['avatar'] != $avatarCache[$sessionMessage['userid']]) {
$sessionMessage['avatar'] = $avatarCache[$sessionMessage['userid']];
// Update $this->sessionName
$this->sessionName ['jchat_user_' . $destination][$msgIndex]['avatar'] = $avatarCache[$sessionMessage['userid']];
}
}
} else {
if(!isset($avatarCache[$sessionMessage['userid']])) {
// Perform di un controllo dei messaggi in sessione inviati senza avatar, ma adesso con avatar aggiunto dall'utente
$isNowNewAvatar = JChatHelpersUsers::getAvatar($sessionMessage['userid']);
$sessionMessage['avatar'] = $isNowNewAvatar;
// Update $this->sessionName
$this->sessionName ['jchat_user_' . $destination][$msgIndex]['avatar'] = $sessionMessage['avatar'];
// Cache storing
$avatarCache[$sessionMessage['userid']] = $sessionMessage['avatar'];
} else {
if($sessionMessage['avatar'] != $avatarCache[$sessionMessage['userid']]) {
$sessionMessage['avatar'] = $avatarCache[$sessionMessage['userid']];
// Update $this->sessionName
$this->sessionName ['jchat_user_' . $destination][$msgIndex]['avatar'] = $avatarCache[$sessionMessage['userid']];
}
}
}
}
}
}
}
/**
* Output response to be formtatted as application/json for JS client
*
* @access protected
* @param array& $messages
* @param array& $wallMessages
* @return void
*/
protected function returnFormattedResponse(&$messages, &$wallMessages = array()) {
if (! empty ( $messages )) {
$this->response ['messages'] = $messages;
}
if(! empty ( $wallMessages)) {
$this->response ['wallmessages'] = $wallMessages;
}
return $this->response;
}
/**
* Updates the user status for typing
*
* @access protected
* @param boolean $typingStatus
* @param string $typingTo
* @return boolean
*/
protected function updateTypingStatus($typingStatus, $typingTo) {
$query = "INSERT INTO #__jchat_sessionstatus (sessionid, " .
$this->dbInstance->quoteName('typing') . "," .
$this->dbInstance->quoteName('typing_to') . ") VALUES (" .
$this->dbInstance->quote($this->userSessionTable->session_id) . ", " .
$this->dbInstance->quote($typingStatus) . ", " .
$this->dbInstance->quote($typingTo) . ") " .
"\n ON DUPLICATE KEY UPDATE " .
$this->dbInstance->quoteName('typing') . " = " . $this->dbInstance->quote($typingStatus) . "," .
$this->dbInstance->quoteName('typing_to') . " = " . $this->dbInstance->quote($typingTo);
$this->dbInstance->setQuery($query);
try {
$this->dbInstance->execute ();
} catch (JChatException $e) {
return false;
} catch (\Exception $e) {
return false;
}
return true;
}
/**
* Detect if live support mode is active and returns query chunks to
* filter users/messages if users is not a chat admins
*
* @access public
* @return void
*/
protected function updateGeolocationIpAddress() {
$isGeolocatedUser = false;
// Check for live support mode active
$geolocationGids = $this->componentParams->get('geolocation_gids', array('0'));
// Live support active!
if(is_array($geolocationGids) && !in_array(0, $geolocationGids, false)) {
// Check for user groups current user belong to
$userGroups = $this->myUser->getAuthorisedGroups();
// Intersect to recognize chat admins
$intersectResult = array_intersect($userGroups, $geolocationGids);
$isGeolocatedUser = (bool)(count($intersectResult));
}
// Eventually limit query to users that belong to chat admins
if($isGeolocatedUser || in_array(0, $geolocationGids, false)) {
$this->storeUserStatus('geoip', $_SERVER['REMOTE_ADDR'], false);
$this->response['geolocation'] = array('status'=>true);
}
}
/**
* Main app dispatch public responsibility
* Execute stream logic and return data to be formatted and sent to JS client
*
* @access public
* @return Object
*/
public function getChatData() {
// Check if the user is not banned before going on
if($this->getBannedStatus()) {
$this->response['loggedout'] = '1';
// Return JS client formatted response
return $this->returnFormattedResponse($this->messages);
}
// Local initialization
$fbchat_user_session_messages = array();
$fbchat_wall_session_messages = array();
// Initialize model state
$forceParams = $this->getState('getparams');
$chatbox = $this->getState('chatbox');
$wallbox = $this->getState('wall');
$wallHistory = $this->getState('wallhistory');
$buddylist = $this->getState('buddylist');
$initialize = $this->getState('initialize');
$update_session = $this->getState('updatesession');
$post_sessionvars = $this->getState('sessionvars');
// Send params back only on initialize AKA first ajax call
if(!empty($this->componentParams) && $initialize) {
$this->componentParams->set('isguest', strval((int)!$this->myUser->id));
// Manage pre-serialization for STUN/TURN servers parameters
$stunServers = $this->componentParams->get('stun_servers', null);
if(!empty($stunServers)) {
$stunServers = explode(PHP_EOL, $stunServers);
$keys = array('urls');
foreach ($stunServers as &$stunServer) {
$stunServer = array_combine($keys, array($stunServer));
}
} else {
$stunServers = array();
}
$turnServers = $this->componentParams->get('turn_servers', null);
if($this->componentParams->get ( 'turn_servers_enabled', 0) && !empty($turnServers)) {
$turnServers = explode(PHP_EOL, $turnServers);
$keys = array('urls', 'username', 'credential');
foreach ($turnServers as &$turnServer) {
$turnServer = explode(',', $turnServer);
if(is_array($turnServer) && count($turnServer) == 3) {
$turnServer = array_combine($keys, $turnServer);
}
}
} else {
$turnServers = array();
}
// Get 24h credentials for Twilio TURN servers
$turnServersAF = array();
$currentTime = time ();
$anyFirewallEnabled = $this->componentParams->get ( 'turn_anyfirewall_enabled', 1);
// Go on only if Twilio TURN is enabled
if ($anyFirewallEnabled && (! isset ( $this->sessionName ['jchat_turn_anyfirewall_ttl'] ) || $currentTime > @$this->sessionName ['jchat_turn_anyfirewall_ttl'])) {
// Check for AnyFirewall valid credentials
$anyFirewallAppname = $this->componentParams->get ( 'turn_twilio_sid', 'AC6f94f1e056ab8560aadc1eace975a251' );
$anyFirewallPassword = $this->componentParams->get ( 'turn_twilio_auth_token', 'a323b78af612f7d7eea9ad8f110ef682' );
// No TURN servers found or it's expired, so go on with a new request
$turnServerCredentials = $this->getTURNServer ( $anyFirewallAppname, $anyFirewallPassword );
// Valid response found
if ($turnServerCredentials) {
// Check if no errors are detected from API
// Store/refresh session lifetime ttl
$this->sessionName ['jchat_turn_anyfirewall_ttl'] = $currentTime + $turnServerCredentials->ttl;
$turnServersAF = $turnServerCredentials->iceServers;
foreach ($turnServersAF as &$turnServerAF) {
$turnServerAF['urls'] = $turnServerAF['url'];
unset($turnServerAF['url']);
}
// Store the retrieved turn server credentials
$this->sessionName ['jchat_turn_anyfirewall'] = $turnServersAF;
}
} elseif ($anyFirewallEnabled && isset ( $this->sessionName ['jchat_turn_anyfirewall'] )) {
// Retrieve from session
$turnServersAF = $this->sessionName ['jchat_turn_anyfirewall'];
}
// Final ICE servers merge
$this->componentParams->set('ice_servers', array_merge($stunServers, $turnServers, $turnServersAF));
// Set user groups for client side ACL permissions
$userGroups = $this->myUser->getAuthorisedGroups();
$this->componentParams->set('usergroups', $userGroups);
// Now translates ACL parameters including child groups
$this->translatesACLParameters(array(
'allow_videochat',
'allow_media_recorder',
'allow_media_recorder_save',
'allow_blackboard',
'allow_livestreaming',
'allow_chatgpt_bot',
'moderation_groups'
));
// Assign super user ACL action, if super user all permissions are allowed
$this->componentParams->set('superuser', $this->myUser->authorise('core.admin'));
$this->componentParams->set('chatform_link', Route::_('index.php?option=com_jchat&view=form'));
// Check access levels intersection to ensure that users has access to add chatrooms
$userGroups = $this->myUser->getAuthorisedGroups();
$this->translatesACLParameters(array('addchatroom_groups', 'deletechatroom_groups'));
$addChatroomGroups = $this->componentParams->get('addchatroom_groups', array(8));
if(is_array($addChatroomGroups) && !in_array(0, $addChatroomGroups, false)) {
$intersectResult = array_intersect($userGroups, $addChatroomGroups);
$hasAddChatroomPermission = (int)(count($intersectResult));
$this->componentParams->set('addchatroom_permission', $hasAddChatroomPermission);
} else {
$this->componentParams->set('addchatroom_permission', 1);
}
$deleteChatroomGroups = $this->componentParams->get('deletechatroom_groups', array(8));
if(is_array($deleteChatroomGroups) && !in_array(0, $deleteChatroomGroups, false)) {
$intersectResult = array_intersect($userGroups, $deleteChatroomGroups);
$hasDeleteChatroomPermission = (int)(count($intersectResult));
$this->componentParams->set('deletechatroom_permission', $hasDeleteChatroomPermission);
} else {
$this->componentParams->set('deletechatroom_permission', 1);
}
$this->componentParams->set('user_access_viewlevels', $this->myUser->getAuthorisedViewLevels());
$this->componentParams->set('total_access_viewlevels', JChatHelpersUsers::getTotalAccessLevels($this->dbInstance));
// If the rooms creation in frontend is enabled inject even the menu tree structure for chatrooms assignments
if($this->componentParams->get('addchatroom', 0) && $this->componentParams->get('addchatroom_permission', 0)) {
$this->componentParams->set('total_menuitems_tree', JChatHtmlMenu::getMenuItems());
}
// Language translation enabled, send the default language iso code for the default fallback
if($this->componentParams->get('language_translation_enabled', 0)) {
$this->componentParams->set('default_fallback_language', JChatHelpersLanguage::getCurrentSefLanguage());
$languageTranslationGroups = $this->componentParams->get('language_translation_groups', array(8));
if(is_array($languageTranslationGroups) && !in_array(0, $languageTranslationGroups, false)) {
$intersectResult = array_intersect($userGroups, $languageTranslationGroups);
$hasLanguageTranslationPermission = (int)(count($intersectResult));
$this->componentParams->set('language_translation_enabled', $hasLanguageTranslationPermission);
}
}
// Final rendering of params to JS domain
$this->response['paramslist'] = $this->componentParams->toObject();
// Remove not needed and params and save bandwidth
unset($this->response['paramslist']->registration_email);
unset($this->response['paramslist']->notification_email);
unset($this->response['paramslist']->email_subject);
unset($this->response['paramslist']->includeevent);
unset($this->response['paramslist']->offline_message);
unset($this->response['paramslist']->ticket_notify_emails);
unset($this->response['paramslist']->tickets_fromname);
unset($this->response['paramslist']->tickets_mailfrom);
unset($this->response['paramslist']->turn_twilio_sid);
unset($this->response['paramslist']->turn_twilio_auth_token);
unset($this->response['paramslist']->addchatroom_groups);
unset($this->response['paramslist']->deletechatroom_groups);
}
if (isset($this->sessionName['jchat_user_' . $chatbox])) {
$fbchat_user_session_messages = $this->sessionName['jchat_user_' . $chatbox];
}
if (isset($this->sessionName['jchat_user_wall'])) {
$fbchat_wall_session_messages = $this->sessionName['jchat_user_wall'];
}
// Assign existant session vars
if(isset($this->sessionName['jchat_sessionvars'])) {
$fbchat_sessionvars = $this->sessionName['jchat_sessionvars'];
}
// Go on with chat data retrieval
if ($this->myUser->id || $this->componentParams->get('guestenabled', false)) {
// Request for a specific chatbox message list
if (!empty($chatbox)) {
if (!empty($fbchat_user_session_messages)) {
if($this->componentParams->get('advanced_avatars_mgmt', false)) {
$this->refreshSessionMessagesAvatars($fbchat_user_session_messages);
}
$this->messages = $fbchat_user_session_messages;
}
// Return JS client formatted response
return $this->returnFormattedResponse($this->messages);
} elseif (!empty($wallbox) && !$wallHistory) {
// Request for public chat messages
if (!empty($fbchat_wall_session_messages)) {
if($this->componentParams->get('advanced_avatars_mgmt', false)) {
$this->refreshSessionMessagesAvatars($fbchat_wall_session_messages, 'wall');
}
$this->wallMessages = $fbchat_wall_session_messages;
}
// Return JS client formatted response
return $this->returnFormattedResponse($this->messages, $this->wallMessages);
} else {
// All other regular requests
if (!empty($buddylist) && $buddylist == 1) {
$this->getBuddyList($initialize);
}
if (!empty($initialize) && $initialize == 1) {
$this->getUserStateFromDB();
// Force start opening mode
if (empty($fbchat_sessionvars) || !isset($fbchat_sessionvars['buddylist'])) {
$fbchat_sessionvars = array();
$startOpenMode = $this->componentParams->get('start_open_mode', 1);
$fbchat_sessionvars['buddylist'] = $startOpenMode;
}
if(!$this->componentParams->get('default_private_chat_sound', 1) && !isset($fbchat_sessionvars['audio'])) {
$fbchat_sessionvars['audio'] = 0;
}
if(!$this->componentParams->get('default_public_chat_sound', 1) && !isset($fbchat_sessionvars['wallaudio'])) {
$fbchat_sessionvars['wallaudio'] = 0;
}
if (!empty($fbchat_sessionvars)) {
$this->response['initialize'] = $fbchat_sessionvars;
$this->wallMessages = $fbchat_wall_session_messages;
}
// Listening for incoming video call from WebRTC signaling channel
if($this->componentParams->get('webrtc_enabled', false) || $this->componentParams->get('enable_blackboard', false) || $this->getState('conferenceview', false)) {
$this->clearSignalingChannel();
}
// Manage the geolcation IP tracking if enabled
if($this->componentParams->get('geolocation_enabled', 0)) {
$this->updateGeolocationIpAddress();
}
} else {
if (empty($fbchat_sessionvars)) {
$fbchat_sessionvars = array();
}
if (!empty($post_sessionvars)) {
ksort($post_sessionvars);
} else {
$post_sessionvars = array();
}
// Check if typing enabled and status has changed
if($this->typingEnabled) {
$sessionTyping = isset($fbchat_sessionvars['typing']) ? $fbchat_sessionvars['typing'] : null;
$postTyping = isset($post_sessionvars['typing']) ? $post_sessionvars['typing'] : null;
$postTypingTo = isset($post_sessionvars['typing_to']) ? $post_sessionvars['typing_to'] : null;
if(!is_null($postTyping) && $sessionTyping != $postTyping) {
$this->updateTypingStatus($postTyping, $postTypingTo);
}
}
// Always update the session vars for languages if informations are sent from the client side in realtime
if(isset($post_sessionvars['langvars'])) {
$this->sessionName['jchat_sessionvars']['langvars'] = $post_sessionvars['langvars'] = json_decode($post_sessionvars['langvars']);
} else {
$this->sessionName['jchat_sessionvars']['langvars'] = null;
}
if (!empty($update_session) && $update_session == 1) {
$this->sessionName['jchat_sessionvars'] = array_merge($fbchat_sessionvars, $post_sessionvars);
}
if ($forceParams) {
$this->response['paramslist'] = $this->componentParams->toObject();
}
}
$this->fetchMessages();
$this->fetchWallMessages();
if($this->typingEnabled) {
$this->fetchTypingStatus();
}
// Listening for incoming video call from WebRTC signaling channel
if($this->componentParams->get('webrtc_enabled', false)) {
$this->fetchSignalingChannel();
}
// Fetch the conference signaling channel only if on the conference view page
if($this->getState('conferenceview', false)) {
$this->fetchConferenceSignalingChannel();
}
// Listening for incoming blackboard shares from WebRTC signaling channel
if($this->componentParams->get('enable_blackboard', false)) {
$this->fetchBlackboardSignalingChannel();
}
// Return JS client formatted response
return $this->returnFormattedResponse($this->messages, $this->wallMessages);
}
} else {
$this->response['loggedout'] = '1';
if ($forceParams) {
$this->response['paramslist'] = $this->componentParams->toObject();
}
// Return JS client formatted response
return $this->returnFormattedResponse($this->messages);
}
}
/**
* Detect if live support mode is active and returns query chunks to
* filter users/messages if users is not a chat admins
*
* @access public
* @return array
*/
public function getQueryLiveSupport($joinTable = 'sess.userid') {
$arrayQueries = array(0=>null, 1=>null);
// Check for live support mode active
$chatAdminsGids = $this->componentParams->get('chatadmins_gids', array('0'));
// Live support active!
if(is_array($chatAdminsGids) && !in_array(0, $chatAdminsGids, false)) {
// Check for user groups current user belong to
$userGroups = $this->myUser->getAuthorisedGroups();
// Intersect to recognize chat admins
$intersectResult = array_intersect($userGroups, $chatAdminsGids);
$isChatAdmin = (bool)(count($intersectResult));
// Eventually limit query to users that belong to chat admins
if(!$isChatAdmin) {
$arrayQueries[0] = "\n INNER JOIN #__user_usergroup_map AS map ON map.user_id = " . $joinTable;
$arrayQueries[1] = "\n AND map.group_id IN (" . implode(',', $chatAdminsGids) . ")";
}
// Inject only on initialize calls
if($this->getState('initialize') == 1) {
$this->response['ischatadmin'] = (int)$isChatAdmin;
}
}
return $arrayQueries;
}
/**
* Translate chat access levels into user groups and generates filtering query
* accordingly for the users taken into account by the buddylist, thus avoiding
* show disabled users into chat of enabled users refreshing session lifetime during navigation
*
* @access public
* @return array
*/
public function getQueryAccessLevels($joinTable = 'sess.userid') {
$arrayQueries = array(0=>null, 1=>null);
// Check for live support mode active
$chatAccessGids = array();
$chatAccessLevels = $this->componentParams->get('chat_accesslevels', array('0'));
// Live support active!
if(is_array($chatAccessLevels) && !in_array(0, $chatAccessLevels, false)) {
// Translate the chat access levels to Joomla users groups sum
$query = method_exists ( $this->dbInstance, 'createQuery' ) ? $this->dbInstance->createQuery () : $this->dbInstance->getQuery ( true );
$query = $query->select('rules')
->from($this->dbInstance->quoteName('#__viewlevels'))
->where('id IN (' . implode(',', $chatAccessLevels) . ')');
// Set the query for execution
$this->dbInstance->setQuery($query);
// Build the view levels array.
foreach ($this->dbInstance->loadColumn() as $levels) {
$chatAccessGids = array_merge($chatAccessGids, (array) json_decode($levels));
}
// Limit query to users that belong to groups for the chosen chat access levels
if(array_intersect(array(1,5), $chatAccessLevels)) {
$arrayQueries[0] = "\n LEFT JOIN #__user_usergroup_map AS accessmap ON accessmap.user_id = " . $joinTable;
$arrayQueries[1] = "\n AND (accessmap.group_id IN (" . implode(',', array_unique($chatAccessGids)) . ") OR ISNULL(accessmap.group_id))";
} else {
$arrayQueries[0] = "\n INNER JOIN #__user_usergroup_map AS accessmap ON accessmap.user_id = " . $joinTable;
$arrayQueries[1] = "\n AND accessmap.group_id IN (" . implode(',', array_unique($chatAccessGids)) . ")";
}
}
return $arrayQueries;
}
/**
* Filter the buddylist based on the current user groups belonging
* Users will be be able to chat only with users in the same users groups
*
* @access public
* @return array
*/
public function getQueryMyUsersGroups($joinTable = 'sess.userid') {
$arrayQueries = array(0=>null, 1=>null);
// Check for user groups current user belong to
$userGroups = $this->myUser->getAuthorisedGroups();
$arrayQueries[0] = "\n INNER JOIN #__user_usergroup_map AS mymap ON mymap.user_id = " . $joinTable;
$arrayQueries[1] = "\n AND mymap.group_id IN (" . implode(',', $userGroups) . ")";
return $arrayQueries;
}
/**
* Get query parts needed for SELECT & JOIN
* tables for database integration component
*
* @access public
* @param string $queryType
* @return array
*/
public function getQueryParts($queryType) {
// Restituisce le query parts in accordo al tipo di integration richiesta
$queryParts = array();
switch ($queryType) {
case 'buddylist' :
switch ($this->integratedExtensions) {
case 'jomsocial':
$queryParts['JOIN'] = "\n INNER JOIN #__community_connection AS cc ON cc.connect_to = u.id";
$queryParts['WHERE'] = "\n AND cc.connect_from = " . (int)$this->myUser->id .
"\n AND cc.status = 1";
break;
case 'cbuilder':
$queryParts['JOIN'] = "\n INNER JOIN #__comprofiler_members AS cm ON cm.memberid = u.id";
$queryParts['WHERE'] = "\n AND cm.referenceid = " . (int)$this->myUser->id .
"\n AND cm.accepted = 1 AND cm.pending = 0";
break;
case 'easysocial':
$queryParts['JOIN'] = "\n LEFT JOIN #__social_friends AS sf ON (sf.actor_id = u.id OR sf.target_id = u.id)" .
$queryParts['WHERE'] = "\n AND (sf.actor_id = " . (int)$this->myUser->id . " OR sf.target_id = " . (int)$this->myUser->id . ")" .
"\n AND sf.state = 1";
break;
case 'k2user':
$queryParts['JOIN'] = '';
$queryParts['WHERE'] = '';
break;
}
break;
case 'groupmessages' :
switch ($this->integratedExtensions) {
case 'jomsocial':
$queryParts['AND'] = " AND u.id IN (SELECT connect_to FROM #__community_connection" .
"\n WHERE connect_from = " . (int)$this->myUser->id . " AND status = 1)";
break;
case 'cbuilder':
$queryParts['AND'] = " AND u.id IN (SELECT memberid FROM #__comprofiler_members" .
"\n WHERE referenceid = " . (int)$this->myUser->id . " AND accepted = 1 and pending = 0)";
break;
case 'easysocial':
$queryParts['AND'] = " AND (u.id IN (SELECT actor_id FROM #__social_friends" .
"\n WHERE target_id = " . (int)$this->myUser->id . " AND state = 1)" .
"\n OR u.id IN (SELECT target_id FROM #__social_friends" .
"\n WHERE actor_id = " . (int)$this->myUser->id . " AND state = 1))";
break;
case 'k2user':
$queryParts['AND'] = '';
break;
}
break;
}
return $queryParts;
}
/**
* Write user status on Stream
*
* @access public
* @param string $fieldName
* @param string $fieldValue
* @param boolean $injectInResponse
* @return array
*/
public function storeUserStatus($fieldName, $fieldValue, $injectInResponse = true) {
$query = "INSERT INTO #__jchat_sessionstatus (sessionid, " . $this->dbInstance->quoteName($fieldName) . ") VALUES (" .
$this->dbInstance->quote($this->userSessionTable->session_id) . ", " .
$this->dbInstance->quote($fieldValue) . ") " .
"\n ON DUPLICATE KEY UPDATE " . $this->dbInstance->quoteName($fieldName) . " = " . $this->dbInstance->quote($fieldValue);
try {
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
if ($fieldName == 'status' && $fieldValue == 'offline') {
$this->sessionName['jchat_sessionvars']['buddylist'] = 0;
}
if($injectInResponse) {
$this->response['storing'] = array('status'=>true);
}
return $this->response;
}
/**
* Write user status on Stream, based on user logging state
*
* @access public
* @param string $statusVarName
* @param string $statusVarValue
* @return array
*/
public function storeUserStateFromRequest($statusVarName, $statusVarValue) {
if(!$this->myUser->id) {
$query = "INSERT INTO #__jchat_sessionstatus (sessionid, " . $this->dbInstance->quoteName($statusVarName) . ") VALUES (" .
$this->dbInstance->quote($this->userSessionTable->session_id) . ", " .
$this->dbInstance->quote($statusVarValue) . ") " .
"ON DUPLICATE KEY UPDATE " . $this->dbInstance->quoteName($statusVarName) . " = " . $this->dbInstance->quote($statusVarValue);
} else {
$query = "INSERT INTO #__jchat_userstatus (userid, " . $this->dbInstance->quoteName($statusVarName) . ") VALUES (" .
$this->dbInstance->quote($this->myUser->id) . ", " .
$this->dbInstance->quote($statusVarValue) . ") " .
"ON DUPLICATE KEY UPDATE " . $this->dbInstance->quoteName($statusVarName) . " = " . $this->dbInstance->quote($statusVarValue);
}
$this->dbInstance->setQuery($query);
try {
$this->dbInstance->execute ();
// If user is now logged in, delete/clean/discard all data in session state
if($this->myUser->id) {
$cleanQuery = "DELETE FROM #__jchat_sessionstatus" .
"\n WHERE " . $this->dbInstance->quoteName('sessionid') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id);
$this->dbInstance->setQuery($cleanQuery)->execute ();
}
// Auto insert a message 'User xxx has joined/left chat room yyy' that will be showed to right users based on receiving logic
$chatroomsNotifications = $this->componentParams->get('chatrooms_notifications', 1);
if($statusVarName == 'roomid' && $statusVarValue > 0) {
if(!$this->app->getInput()->get('silentJoin')) {
// User changed chatroom, add a left message for the previous chatroom
if($this->myChatRoom && $this->myChatRoom != $statusVarValue && $chatroomsNotifications) {
$this->storeGroupMessage('wall', Text::sprintf('COM_JCHAT_LEFT', '<img class="jchat_roomenter" src="' . Uri::base() . 'components/com_jchat/images/default/room_left.png"/>'), true);
}
$this->myChatRoom = $statusVarValue;
if($chatroomsNotifications) {
$this->storeGroupMessage('wall', Text::sprintf('COM_JCHAT_JOINED', '<img class="jchat_roomenter" src="' . Uri::base() . 'components/com_jchat/images/default/room_enter.png"/>'), true);
}
} else {
$this->myChatRoom = $statusVarValue;
}
// Store just joined room state to recover latest xxx time messages of current conversation
if($this->componentParams->get('chatrooms_latest', 1)) {
$this->sessionName['jchat_justjoined'] = true;
}
} elseif($statusVarName == 'roomid' && $statusVarValue == 0) {
if($chatroomsNotifications) {
$this->storeGroupMessage('wall', Text::sprintf('COM_JCHAT_LEFT', '<img class="jchat_roomenter" src="' . Uri::base() . 'components/com_jchat/images/default/room_left.png"/>'), true);
}
$this->myChatRoom = 0;
}
// Ensure to clear the current public chat session messages
if($statusVarName == 'roomid' && $this->componentParams->get('autoclear_conversation', 1)) {
$this->sessionName['jchat_user_wall'] = array();
}
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
$this->response['storing'] = array('status'=>true);
return $this->response;
}
/**
* Store banning state for current session id user
*
* @param Object $bannedUserInfo
* @access public
* @return array
*/
public function storeBannedUsersState($bannedUserInfo) {
if(!$bannedUserInfo->currentState) {
$query = "DELETE FROM #__jchat_banned_users" .
"\n WHERE " .
$this->dbInstance->quoteName('banning') . " = " . $this->dbInstance->quote($this->userSessionTable->session_id) .
"\n AND " .
$this->dbInstance->quoteName('banned') . " = " . $this->dbInstance->quote($bannedUserInfo->userSessionId);
} else {
$query = "INSERT IGNORE INTO #__jchat_banned_users (" .
$this->dbInstance->quoteName('banning') . ", " .
$this->dbInstance->quoteName('banned') .
") VALUES (" .
$this->dbInstance->quote($this->userSessionTable->session_id) . ", " .
$this->dbInstance->quote($bannedUserInfo->userSessionId) .
")";
}
try {
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
// If this is a registered and logged in user, store also the real user id for propagate banning to future sessions
if(isset($bannedUserInfo->userId) && $this->myUser->id) {
if(!$bannedUserInfo->currentState) {
$query = "DELETE FROM #__jchat_banned_users_ids" .
"\n WHERE " .
$this->dbInstance->quoteName('banning') . " = " . $this->dbInstance->quote($this->myUser->id) .
"\n AND " .
$this->dbInstance->quoteName('banned') . " = " . $this->dbInstance->quote($bannedUserInfo->userId);
} else {
$query = "INSERT IGNORE INTO #__jchat_banned_users_ids (" .
$this->dbInstance->quoteName('banning') . ", " .
$this->dbInstance->quoteName('banned') .
") VALUES (" .
$this->dbInstance->quote($this->myUser->id) . ", " .
$this->dbInstance->quote($bannedUserInfo->userId) .
")";
}
try {
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
}
$this->response['storing'] = array('status'=>true);
return $this->response;
}
/**
* Write user status on Stream, based on user logging state
*
* @access public
* @param Object $bannedUserInfo
* @return array
*/
public function storeBannedModeratedUsersState($bannedUserInfo) {
if(!isset($bannedUserInfo->userId)) {
$query = "INSERT INTO #__jchat_sessionstatus (sessionid, " . $this->dbInstance->quoteName('banstatus') . ") VALUES (" .
$this->dbInstance->quote($bannedUserInfo->userSessionId) . ", 1)" .
"ON DUPLICATE KEY UPDATE " . $this->dbInstance->quoteName('banstatus') . " = 1";
} else {
$query = "INSERT INTO #__jchat_userstatus (userid, " . $this->dbInstance->quoteName('banstatus') . ") VALUES (" .
$this->dbInstance->quote($bannedUserInfo->userId) . ", 1)" .
"ON DUPLICATE KEY UPDATE " . $this->dbInstance->quoteName('banstatus') . " = 1";
}
try {
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
$this->response['storing'] = array('status'=>true);
return $this->response;
}
/**
* Write private message on Stream
*
* @access public
* @param string $to
* @param int $tologged
* @param string $message
* @param Object $mailer
* @param string $chatGptFrom
* @return array
*/
public function storePrivateMessage($to, $tologged, $message, $mailer, $chatGptFrom = null) {
if ($this->userSessionTable->session_id) {
// Set initial state
$pmStoreMessage = false;
$translatedMessage = null;
$selfTranslate = false;
// Valid target user session id?
if(!$to && $tologged) {
$sessionSql = "SELECT" .
"\n " . $this->dbInstance->quoteName('session_id') .
"\n FROM #__session" .
"\n WHERE" .
"\n " . $this->dbInstance->quoteName('userid') . " = " . (int)$tologged .
"\n ORDER BY " . $this->dbInstance->quoteName('time') . " DESC" .
"\n LIMIT 1";
$this->dbInstance->setQuery($sessionSql);
$sessionIDReceiver = $this->dbInstance->loadResult();
$to = $sessionIDReceiver ? $sessionIDReceiver : -1;
// Translate to a private message to an offline user
$pmStoreMessage = true;
}
// Language translation of the message
if($this->componentParams->get('language_translation_enabled', 0)) {
// Get post informations and ensure that the language translation request is valid
$sourceLanguage = $this->app->getInput()->get('sourcelang');
$targetLanguage = $this->app->getInput()->get('targetlang');
$langSwitchEnabled = $this->app->getInput()->getInt('lang_switch_enabled', 0);
if($langSwitchEnabled && ($sourceLanguage != $targetLanguage)) {
try {
// Composer autoloader
require_once JPATH_COMPONENT_ADMINISTRATOR . '/Framework/composer/autoload_real.php';
$translatedMessage = \Stichoza\GoogleTranslate\TranslateClient::translate($sourceLanguage, $targetLanguage, $message);
if($this->componentParams->get('language_translation_selfmessages', 1)) {
$message = $translatedMessage ? $translatedMessage : $message;
$selfTranslate = true;
}
} catch(\Exception $e) {/*Do nothing, leave the original text message unaltered*/}
}
}
// Get users actual names
$actualNames = JChatHelpersUsers::getActualNames ( $this->userSessionTable->session_id, $to, $this->componentParams );
$unixTimeStamp = time();
$insertMessageSessionTo = $chatGptFrom == null ? $this->userSessionTable->session_id : $chatGptFrom;
$insertMessageUserTo = $chatGptFrom == null ? $this->myUser->id : 0;
$insertMessageActualFrom = $chatGptFrom == null ? $actualNames['fromActualName'] : $this->componentParams->get('chatgpt_ai_bot_name', 'ChatBot');
$sql = "INSERT INTO #__jchat" .
"\n (#__jchat.from," .
"\n #__jchat.to," .
"\n #__jchat.fromuser," .
"\n #__jchat.touser," .
"\n #__jchat.message," .
"\n #__jchat.sent," .
"\n #__jchat.read," .
"\n #__jchat.actualfrom," .
"\n #__jchat.actualto," .
"\n #__jchat.ipaddress) VALUES (".
$this->dbInstance->quote($insertMessageSessionTo) . ", " .
$this->dbInstance->quote($to) . "," .
$this->dbInstance->quote($insertMessageUserTo) . "," .
$this->dbInstance->quote($tologged) . "," .
$this->dbInstance->quote($translatedMessage ? $translatedMessage : $message) . "," .
$this->dbInstance->quote($unixTimeStamp) . "," .
"0" . "," .
$this->dbInstance->quote($insertMessageActualFrom) . "," .
$this->dbInstance->quote($actualNames['toActualName']) . "," .
$this->dbInstance->quote($_SERVER['REMOTE_ADDR']). ")";
try {
$this->dbInstance->setQuery($sql);
$this->dbInstance->execute ();
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
// Return here if there is a ChatGPT storing message
if($chatGptFrom) {
// Ensure no typing status are more active
$this->updateTypingStatus(null, null);
return [];
}
// Send email notification if needed, AKA new conversation started and settings are required
if(!isset($this->sessionName['jchat_user_'.$to]) && $this->componentParams->get('notification_email_switcher', false)) {
// Format and send email
$notificationsAddresses = $this->componentParams->get('notification_email', null);
if($notificationsAddresses) {
$mailer->IsHTML(true);
// Get single email addresses
$exploded = explode(',', $notificationsAddresses);
foreach ($exploded as $recipient) {
$mailer->addRecipient(trim($recipient));
}
// Set subject
$mailer->setSubject($this->componentParams->get('email_subject', 'JChatSocial - New conversation started'));
// Format body and message
$body = Text::sprintf('COM_JCHAT_NEWUSER_CONVERSATION', $actualNames['fromActualName'], $actualNames['toActualName']);
$customText =Text::sprintf('COM_JCHAT_STARTING_CUSTOMTEXT_FORMATTED', $this->componentParams->get('email_start_text', ''));
$messageText = Text::sprintf('COM_JCHAT_STARTING_MESSAGE', $actualNames['fromActualName'], $message, $customText, Uri::root());
$mailer->setBody($body . $messageText);
$result = $mailer->sendUsingExceptions();
}
}
// Send email notification if offline private message sent to offline user and the option is enabled
if($pmStoreMessage) {
$pmNotifications = $this->componentParams->get('private_messaging_notification_email', true);
// Private messaging notifications enabled
if($pmNotifications) {
// Try to retrieve the user avatar
$userAvatar = JChatHelpersUsers::getAvatar($this->userSessionTable->session_id);
// Retrieve name of the sender and email of the target user
$userEmailSql = "SELECT" .
"\n " . $this->dbInstance->quoteName('email') .
"\n FROM" . $this->dbInstance->quoteName('#__users') .
"\n WHERE" .
"\n " . $this->dbInstance->quoteName('id') . " = " . (int)$tologged;
$this->dbInstance->setQuery($userEmailSql);
$recipient = $this->dbInstance->loadResult();
$mailer->IsHTML(true);
$mailer->addRecipient(trim($recipient));
// Set subject
$mailer->setSubject(Text::sprintf('COM_JCHAT_USER_SENT_PM_TOYOU', $actualNames['fromActualName']));
// Format body and message
$body = Text::sprintf('COM_JCHAT_USER_SENT_PM_INTRO', $userAvatar, $actualNames['fromActualName']);
$messageText = Text::sprintf('COM_JCHAT_USER_SENT_PM_MESSAGE', $message);
$messageReply = Text::sprintf('COM_JCHAT_USER_SENT_PM_MESSAGE_ANSWER', Uri::base(), Uri::base(), Uri::base());
$mailer->setBody($body . $messageText . $messageReply);
$result = $mailer->sendUsingExceptions();
}
}
if (empty($this->sessionName['jchat_user_'.$to])) {
$this->sessionName['jchat_user_'.$to] = [];
}
// Store local session message
$lastInsertId = $this->dbInstance->insertid();
$insertTime = HTMLHelper::_('date', $unixTimeStamp, Text::_('DATE_FORMAT_LC2'));
$this->sessionName['jchat_user_'.$to][$lastInsertId] = array("id" => $lastInsertId,
"from" => $to,
"message" => $message,
"time" => $insertTime,
"self" => 1,
"old" => 1);
$this->response['storing'] = array('status'=>true, 'details'=>array('id'=>$lastInsertId, 'time'=>$insertTime));
if($selfTranslate) {
$this->response['storing']['translatedmessage'] = $translatedMessage ? $translatedMessage : $message;
}
// Ensure no typing status are more active
$this->updateTypingStatus(null, null);
return $this->response;
}
}
/**
* Write group message on Stream
*
* @access public
* @param int $to
* @param string $message
* @param boolean $skipSession
* @return array
*/
public function storeGroupMessage($to, $message, $skipSession = false) {
if ($this->userSessionTable->session_id) {
// Get users actual names
$actualNames = JChatHelpersUsers::getActualNames ( $this->userSessionTable->session_id, $to, $this->componentParams );
$unixTimeStamp = time();
$query = "INSERT INTO #__jchat" .
"\n (#__jchat.from," .
"\n #__jchat.to," .
"\n #__jchat.message," .
"\n #__jchat.sent," .
"\n #__jchat.read," .
"\n #__jchat.actualfrom," .
"\n #__jchat.sentroomid," .
"\n #__jchat.ipaddress) VALUES (".
$this->dbInstance->quote($this->userSessionTable->session_id) . ", " .
"0" . "," .
$this->dbInstance->quote($message) . "," .
$this->dbInstance->quote($unixTimeStamp) . "," .
"0" . "," .
$this->dbInstance->quote($actualNames['fromActualName']) . "," .
(int)$this->myChatRoom . "," .
$this->dbInstance->quote($_SERVER['REMOTE_ADDR']) . ")";
try {
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
if (empty($this->sessionName['jchat_user_'.$to])) {
$this->sessionName['jchat_user_'.$to] = array();
}
// Store local session message
$lastInsertId = $this->dbInstance->insertid();
$insertTime = HTMLHelper::_('date', $unixTimeStamp, Text::_('DATE_FORMAT_LC2'));
if(!$skipSession) {
$this->sessionName['jchat_user_'.$to][$lastInsertId] = array("id" => $lastInsertId,
"fromuser" => $this->myUser->username,
"from" => $to,
"message" => $message,
"time" => $insertTime,
"self" => 1,
"old" => 1);
}
$this->response['storing'] = array('status'=>true, 'details'=>array('id'=>$lastInsertId, 'time'=>$insertTime));
return $this->response;
}
}
/**
* Add a new chatroom to the database
*
* @access public
* @param string $roomName
* @param string $roomDescription
* @param int $roomAccess
* @param string $roomMenuitems
* @return array
*/
public function storeNewChatroom($roomName, $roomDescription, $roomAccess, $roomMenuItems) {
$sqlMenuItems = $roomMenuItems ? $this->dbInstance->quote($roomMenuItems) : 'NULL';
$query = "INSERT INTO #__jchat_rooms (" .
"\n " . $this->dbInstance->quoteName('name') . "," .
"\n " . $this->dbInstance->quoteName('description') . "," .
"\n " . $this->dbInstance->quoteName('access') . "," .
"\n " . $this->dbInstance->quoteName('ordering') . "," .
"\n " . $this->dbInstance->quoteName('menuitems') . ")" .
"\n VALUES (" .
$this->dbInstance->quote($roomName) . ", " .
$this->dbInstance->quote($roomDescription) . ", " .
(int)$roomAccess . ", " .
"\n COALESCE((SELECT MAX(" .$this->dbInstance->quoteName('ordering') . ") FROM " .
"\n (SELECT * FROM #__jchat_rooms) AS " . $this->dbInstance->quoteName('inrtable') . "), 0) + 1" . ", " .
$sqlMenuItems . ')';
try {
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
// Clean the component cache to refresh immediately
JChatHelpersMessages::cleanComponentCache('com_jchat', 0);
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
$this->response['storing'] = array('status'=>true, 'chatroomid'=>$this->dbInstance->insertid());
return $this->response;
}
/**
* Delete conversation from session
*
* @access public
* @param int $from
* @return array
*/
public function deleteConversation($from) {
if (isset($this->sessionName['jchat_user_'.$from])) {
// 1) Get file messages
if(is_array($this->sessionName['jchat_user_'.$from]) && $from != 'wall') {
$idsToUpdate = array();
foreach ($this->sessionName['jchat_user_'.$from] as $genericMsg) {
// Select only file messages ids
if(isset($genericMsg['type']) && $genericMsg['type'] === 'file') {
$idsToUpdate[] = $genericMsg['id'];
}
}
}
// 2) Flag as clientdeleted on DB
if(!empty($idsToUpdate)) {
$query = "UPDATE #__jchat SET " . $this->dbInstance->quoteName('clientdeleted') . " = 1" .
"\n WHERE " . $this->dbInstance->quoteName('id') . " IN (" . implode(',', $idsToUpdate) . ")";
try {
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
}
// 3) Empty session array
$this->sessionName['jchat_user_'.$from] = array();
}
$this->response['storing'] = array('status'=>true);
return $this->response;
}
/**
* Delete a single message in 3 steps:
*
* 1) Delete it from the local sender session, being it a private message or a wall message
* 2) Add a physical deleted message row to the dedicated table, this is required for the clients fetch
* 3) Physically delete the sent message from the main table, this will affect all messaging types and logged in/out states by future fetches
*
* @access public
* @param int $deletedMessageId
* @param int $to
* @param int $deletedMessagePM
* @return array
*/
public function deleteSingleMessage($deletedMessageId, $to, $deletedMessagePM) {
if (isset($this->sessionName['jchat_user_'.$to]) || $deletedMessagePM) {
// 1) Delete the message from the session
if(isset($this->sessionName['jchat_user_'.$to]) && is_array($this->sessionName['jchat_user_'.$to]) && isset($this->sessionName['jchat_user_'.$to][$deletedMessageId])) {
// Unset this message from the session array
unset($this->sessionName['jchat_user_'.$to][$deletedMessageId]);
}
try {
// 2) Add the physical deleted message
$query = "INSERT INTO #__jchat_deleted_messages" .
"\n (" .
"\n #__jchat_deleted_messages.messageid," .
"\n #__jchat_deleted_messages.from," .
"\n #__jchat_deleted_messages.to," .
"\n #__jchat_deleted_messages.sent) VALUES (".
$this->dbInstance->quote($deletedMessageId) . ", " .
$this->dbInstance->quote($this->userSessionTable->session_id) . ", " .
$this->dbInstance->quote($to) . ", " .
time() . ")";
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
// 3) Flag as clientdeleted on DB
$query = "DELETE FROM #__jchat WHERE " . $this->dbInstance->quoteName('id') . " = " . $deletedMessageId;
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
}
$this->response['storing'] = array('status'=>true);
return $this->response;
}
/**
* Delete chatroom from the frontend stream
*
* @access public
* @param int $chatroomID
* @return array
*/
public function deleteChatroom($chatroomID) {
if(!empty($chatroomID)) {
$query = "DELETE FROM #__jchat_rooms" .
"\n WHERE " . $this->dbInstance->quoteName('id') . " = " . (int)$chatroomID;
try {
$this->dbInstance->setQuery($query);
$this->dbInstance->execute ();
// Clean the component cache to refresh immediately
JChatHelpersMessages::cleanComponentCache('com_jchat', 0);
} catch (JChatException $e) {
$this->response['storing'] = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response['storing'] = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
}
$this->response['storing'] = array('status'=>true);
return $this->response;
}
/**
* Retrieve guest info user informations, usually stored by guest activation form
*
* @access public
* @param string $session_id
* @return array
*/
public function getInfoGuest($session_id) {
// Empty initialization
$resultInfo = array();
$query = "SELECT " . $this->dbInstance->quoteName('email') . "," .
$this->dbInstance->quoteName('description') .
"\n FROM " . $this->dbInstance->quoteName('#__jchat_sessionstatus') .
"\n WHERE " . $this->dbInstance->quoteName('sessionid') . " = " . $this->dbInstance->quote($session_id);
try {
$this->dbInstance->setQuery($query);
$resultInfo = $this->dbInstance->loadObject ();
} catch (JChatException $e) {
$this->response = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
$this->response = array('status'=>true, 'details'=>$resultInfo);
return $this->response;
}
/**
* Load chatrooms from DB using caching system
*
* @access private
* @param int $menuItemid
* @return array
*/
public function loadChatRooms($menuItemid) {
// Get user access levels to filter rooms access
$viewLevels = implode(',', $this->myUser->getAuthorisedViewLevels());
// Get menu Itemid
$menuFiltering = null;
if($menuItemid) {
$menuFiltering = "\n AND (ISNULL(rooms.menuitems) OR rooms.menuitems REGEXP '(^|[^0-9])$menuItemid([^0-9]|$)')";
}
$sql = "SELECT rooms.id, rooms.name, 0 AS numusers, NULL AS users," .
"\n rooms.description, rooms.ordering" .
"\n FROM #__jchat_rooms AS rooms" .
"\n WHERE rooms.published = 1" .
"\n AND rooms.access IN (" . $viewLevels . ")" .
$menuFiltering .
"\n ORDER BY rooms.ordering ASC";
$this->dbInstance->setQuery($sql);
$rooms = $this->dbInstance->loadAssocList('ordering');
return $rooms;
}
/**
* Get user profile based on integration type
*
* @access public
* @param int $id
* @param string $name
* @param Object $cParams
* @return string
*/
public function formatUserProfileLink($id, $name, $cParams) {
$profileLink = null;
$Itemid = $this->componentParams->get('social_menu_item', 0);
$Itemid = $Itemid ? '&Itemid=' . $Itemid : null;
// The Joomla user profile link takes always the highest priority if enabled
if ($cParams->get ( 'joomlauser_profilelink', 0 )) {
$query = method_exists ( $this->dbInstance, 'createQuery' ) ? $this->dbInstance->createQuery () : $this->dbInstance->getQuery ( true );
$query->select('MAX(contact.id) AS contactid, contact.alias, contact.catid');
$query->from($this->dbInstance->quoteName('#__contact_details', 'contact'));
$query->where('contact.published = 1');
$query->where('contact.user_id = ' . (int) $id);
if (Multilanguage::isEnabled() === true) {
$query->where('(contact.language in '
. '(' . $this->dbInstance->quote(Factory::getApplication()->getLanguage()->getTag()) . ',' . $this->dbInstance->quote('*') . ') '
. ' OR contact.language IS NULL)');
}
$this->dbInstance->setQuery($query);
$contactObject = $this->dbInstance->loadObject();
if(!$contactObject->contactid) {
return null;
}
$contactLink = Route::_(\Joomla\Component\Contact\Site\Helper\RouteHelper::getContactRoute($contactObject->contactid . ':' . $contactObject->alias, $contactObject->catid));
return $contactLink;
}
$integrationType = $this->componentParams->get('3pdintegration', null);
// Evaluate if integration type is activated
if($integrationType === 'jomsocial') {
// Format fo JomSocial
$profileLink = Route::_('index.php?option=com_community&view=profile&userid=' . $id . $Itemid);
} elseif($integrationType === 'easysocial') {
// Format for EasySocial users
if($cParams->get('usefullname', 'username') == 'name') {
$transformUser = Factory::getContainer()->get(\Joomla\CMS\User\UserFactoryInterface::class)->loadUserById($id);
$name = $transformUser->username;
}
$formattedName = OutputFilter::stringURLSafe($name);
if($this->componentParams->get('easysocial_profilelink_id', 1)) {
$profileLink = Route::_('index.php?option=com_easysocial&view=profile&id=' . $id . '-' . $formattedName . $Itemid);
} else {
$profileLink = Route::_('index.php?option=com_easysocial&view=profile&id=' . $formattedName . $Itemid);
}
} elseif($integrationType === 'cbuilder') {
global $_CB_framework;
// Format for CB users
if (! file_exists ( JPATH_ADMINISTRATOR . '/components/com_comprofiler/plugin.foundation.php' )) {
// Format for CB users
$profileLink = Route::_('index.php?option=com_comprofiler&task=userprofile&user=' . $id . $Itemid);
return $profileLink;
}
include_once (JPATH_ADMINISTRATOR . '/components/com_comprofiler/plugin.foundation.php');
cbimport ( 'cb.html' );
cbimport ( 'language.front' );
$profileLink = $_CB_framework->userProfileUrl ( $id );
} elseif($integrationType === 'kunena') {
// Format for Kunena users
$profileLink = Route::_('index.php?option=com_kunena&view=user&userid=' . $id . $Itemid);
} elseif($integrationType === 'k2user') {
// Format for K2 users
if($cParams->get('usefullname', 'username') == 'username') {
$transformUser = Factory::getContainer()->get(\Joomla\CMS\User\UserFactoryInterface::class)->loadUserById($id);
$name = $transformUser->name;
}
$formattedName = OutputFilter::stringURLSafe($name);
$profileLink = Route::_('index.php?option=com_k2&view=itemlist&task=user&id=' . $id . ':' . $formattedName . $Itemid);
}
return $profileLink;
}
/**
* Retrieve old messages based on time period and private conversation
* between to logged in and registered users
*
* @access public
* @param int $fromLoggedID
* @param string $fromUserID
* @param string $timePeriod
* @param int $minMessageId
* @return array
*/
public function fetchHistoryMessages($fromLoggedID, $fromUserID, $timePeriod, $minMessageId) {
// Empty initialization
$historyMessages = array();
$limitMessages = null;
// Calculate starting period to retrieve messages
switch($timePeriod) {
case '1d':
$periodTimeSeconds = 60 * 60 * 24;
$minMessagesTime = time() - $periodTimeSeconds;
break;
case '1w':
$periodTimeSeconds = 60 * 60 * 24 * 7;
$minMessagesTime = time() - $periodTimeSeconds;
break;
case '1m':
$periodTimeSeconds = 60 * 60 * 24 * 30;
$minMessagesTime = time() - $periodTimeSeconds;
break;
case '3m':
$periodTimeSeconds = 60 * 60 * 24 * 90;
$minMessagesTime = time() - $periodTimeSeconds;
break;
case '6m':
$periodTimeSeconds = 60 * 60 * 24 * 180;
$minMessagesTime = time() - $periodTimeSeconds;
break;
case '1y':
$periodTimeSeconds = 60 * 60 * 24 * 365;
$minMessagesTime = time() - $periodTimeSeconds;
break;
}
try {
$filter = InputFilter::getInstance();
$userFieldName = $filter->clean($this->componentParams->get('usefullname', 'username'), 'word');
// Exclude already showed and exchanged messages if any
if((bool)$minMessageId) {
$limitMessages = "\n AND cchat.id < " . (int)$minMessageId;
}
$sql = "SELECT cchat.id, " .
"\n " . $this->dbInstance->quote($fromUserID) . " AS " . $this->dbInstance->quoteName('from') . "," .
"\n " . $this->dbInstance->quote($this->userSessionTable->session_id) . " AS " . $this->dbInstance->quoteName('to') . "," .
"\n cchat.message, cchat.sent, cchat.read, cchat.type, cchat.status, u.id AS userid, " .
"\n u.$userFieldName AS " . $this->dbInstance->quoteName('fromusername') .
"\n FROM #__jchat AS cchat" .
"\n INNER JOIN #__users AS u ON cchat.fromuser = u.id" .
"\n WHERE ((cchat.touser = ". $this->dbInstance->quote($this->myUser->id) .
"\n AND cchat.fromuser = " . $this->dbInstance->quote($fromLoggedID) . ")" .
"\n OR (cchat.fromuser = ". $this->dbInstance->quote($this->myUser->id) .
"\n AND cchat.touser = " . $this->dbInstance->quote($fromLoggedID) . "))" .
"\n AND cchat.sent > " . (int)$minMessagesTime .
$limitMessages .
"\n ORDER BY cchat.id ASC";
$this->dbInstance->setQuery($sql);
$rows = $this->dbInstance->loadAssocList();
if(is_array($rows) && count($rows)) {
// Add new received messages on stream if any
foreach ($rows as $chatmessage) {
$self = $chatmessage['userid'] == $this->myUser->id ? 1 : 0;
// Get profile link
$chatmessage['profilelink'] = $this->getUserProfileLink($chatmessage['userid'], $chatmessage['fromusername'], $this->componentParams);
$messageUserTime = HTMLHelper::_('date', $chatmessage['sent'], Text::_('DATE_FORMAT_LC2'));
$historyMessages[] = array( 'id' => $chatmessage['id'],
'from' => $chatmessage['from'],
'fromusername' => @$chatmessage['fromusername'],
'profilelink' => @$chatmessage['profilelink'],
'message' => stripslashes($chatmessage['message']),
'type' => @$chatmessage['type'],
'status' => @$chatmessage['status'],
'time' => $messageUserTime,
'self' => $self,
'old' => 0);
// Store new streamed messages into session if not own messages, old messages and already read
$this->sessionName['jchat_user_'.$chatmessage['from']][$chatmessage['id']] = array(
'id' => $chatmessage['id'],
'from' => $chatmessage['from'],
'fromusername' => @$chatmessage['fromusername'],
'profilelink' => @$chatmessage['profilelink'],
'message' => stripslashes($chatmessage['message']),
'type' => @$chatmessage['type'],
'status' => @$chatmessage['status'],
'time' => $messageUserTime,
'self' => $self,
'old' => 1);
}
}
} catch (JChatException $e) {
$this->response = array('status'=>false, 'details'=>$e->getMessage());
return $this->response;
} catch (\Exception $e) {
$jchatException = new JChatException($e->getMessage(), 'error');
$this->response = array('status'=>false, 'details'=>$jchatException->getMessage());
return $this->response;
}
if($minMessageId !== null) {
$this->response = array('status'=>true, 'messages'=>$historyMessages);
} else {
if(!isset($this->response['messages'])) {
$this->response['messages'] = array();
}
$this->response['messages'] = array_merge($this->response['messages'], $historyMessages);
}
return $this->response;
}
/**
* Class contructor
*
* @access public
* @param $config array
* @return Object
*/
public function __construct($config = array(), ?MVCFactoryInterface $factory = null) {
$this->getComponentParams();
$this->response = array();
$this->messages = array();
$this->wallMessages = array();
$this->myUser = Factory::getApplication()->getIdentity();
$this->integratedExtensions = $this->componentParams->get('3pdintegration', null);
$this->typingStatusChanged = false;
$this->typingTo = null;
$this->typingEnabled = $this->componentParams->get('typing_enabled', true);
$this->meetingsFeatureEnabled = $this->componentParams->get('guestenabled', 1) == 2 && $this->componentParams->get('meetings_enabled', 0);
// User session table instance
$this->userSessionTable = $config['sessiontable'];
parent::__construct ( $config, $factory );
// Evaluate the shared session option for SQL queries
$sharedSession = (int)$this->app->get('shared_session', null);
if($sharedSession == 1 && $this->componentParams->get('shared_session_support', 1)) {
$this->sessClientId = '(sess.client_id = 0 OR ISNULL(sess.client_id))';
$this->sessionClientId = '(session.client_id = 0 OR ISNULL(session.client_id))';
} else {
$this->sessClientId = 'sess.client_id = 0';
$this->sessionClientId = 'session.client_id = 0';
}
// Patch for propagating DB session lifetime if the native PHP sessions handler is active
$config = $this->app->getConfig ();
if($config->get ( 'session_handler' ) != 'database') {
try {
$time = time();
// The modulus introduces a little entropy, making the flushing less accurate but fires the query less than half the time.
$query = method_exists ( $this->dbInstance, 'createQuery' ) ? $this->dbInstance->createQuery () : $this->dbInstance->getQuery ( true );
$query->update($this->dbInstance->quoteName('#__session'))
->set($this->dbInstance->quoteName('time') . ' = ' . $time)
->where($this->dbInstance->quoteName('session_id') . ' = ' . $this->dbInstance->quote($this->userSessionTable->session_id));
$this->dbInstance->setQuery($query);
$this->dbInstance->execute();
} catch (\Exception $e) {
// No errors managed, leave the process go on anyway
}
}
// Setup the live streaming feature
$query = method_exists ( $this->dbInstance, 'createQuery' ) ? $this->dbInstance->createQuery () : $this->dbInstance->getQuery ( true );
$query->select('id')
->from($this->dbInstance->quoteName('#__menu'))
->where($this->dbInstance->quoteName('link') . ' = ' . $this->dbInstance->quote('index.php?option=com_jchat&view=livestreaming'))
->where($this->dbInstance->quoteName('published') . ' = 1' );
// Set the query for execution
$this->dbInstance->setQuery($query);
$this->livestreamingFeatureEnabled = $this->dbInstance->loadResult();
// Set my chatroom if chatrrom mode is enabled, for buddylist my chatroom users filtering and stream messages
$this->myChatRoom = $this->getMyChatRoom();
}
}